From d1445a3618f8ed474e3731accf06286bf9024817 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 12:31:25 -0500 Subject: [PATCH 01/10] Add Angular wrappers and tests for ok-components fv directives --- ...-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json | 7 + .../fv/fv-accordion-item-section.component.ts | 4 +- .../customapp/fv/fv-card-section.component.ts | 22 ++ .../fv/fv-chip-selector-section.component.ts | 17 + .../fv/fv-context-help-section.component.ts | 18 + .../app/customapp/fv/fv-section.component.ts | 6 + .../src/app/customapp/fv/fv-section.module.ts | 41 ++- ...v-split-button-anchor-section.component.ts | 22 ++ .../fv/fv-split-button-section.component.ts | 18 + .../fv/fv-summary-panel-section.component.ts | 17 + .../ok-angular/fv/card/ng-package.json | 6 + .../fv/card/ok-fv-card.directive.ts | 86 +++++ .../ok-angular/fv/card/ok-fv-card.module.ts | 12 + .../ok-angular/fv/card/public-api.ts | 2 + .../card/tests/ok-fv-card.directive.spec.ts | 272 +++++++++++++++ .../fv/chip-selector/ng-package.json | 6 + .../ok-fv-chip-selector.directive.ts | 84 +++++ .../ok-fv-chip-selector.module.ts | 12 + .../ok-angular/fv/chip-selector/public-api.ts | 2 + .../ok-fv-chip-selector.directive.spec.ts | 266 +++++++++++++++ .../fv/context-help/ng-package.json | 6 + .../ok-fv-context-help.directive.ts | 59 ++++ .../context-help/ok-fv-context-help.module.ts | 12 + .../ok-angular/fv/context-help/public-api.ts | 2 + .../ok-fv-context-help.directive.spec.ts | 200 +++++++++++ .../ok-fv-search-input.directive.spec.ts | 90 +++++ .../fv/split-button-anchor/ng-package.json | 6 + .../ok-fv-split-button-anchor.directive.ts | 104 ++++++ .../ok-fv-split-button-anchor.module.ts | 12 + .../fv/split-button-anchor/public-api.ts | 2 + ...k-fv-split-button-anchor.directive.spec.ts | 314 ++++++++++++++++++ .../fv/split-button/ng-package.json | 6 + .../ok-fv-split-button.directive.ts | 68 ++++ .../split-button/ok-fv-split-button.module.ts | 12 + .../ok-angular/fv/split-button/public-api.ts | 2 + .../ok-fv-split-button.directive.spec.ts | 219 ++++++++++++ .../fv/summary-panel-tile/ng-package.json | 6 + .../ok-fv-summary-panel-tile.directive.ts | 68 ++++ .../ok-fv-summary-panel-tile.module.ts | 12 + .../fv/summary-panel-tile/public-api.ts | 2 + ...ok-fv-summary-panel-tile.directive.spec.ts | 224 +++++++++++++ .../fv/summary-panel/ng-package.json | 6 + .../ok-fv-summary-panel.directive.ts | 48 +++ .../ok-fv-summary-panel.module.ts | 12 + .../ok-angular/fv/summary-panel/public-api.ts | 2 + .../ok-fv-summary-panel.directive.spec.ts | 176 ++++++++++ 46 files changed, 2586 insertions(+), 4 deletions(-) create mode 100644 change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts create mode 100644 packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts create mode 100644 packages/angular-workspace/ok-angular/fv/card/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/card/ok-fv-card.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/card/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/chip-selector/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/chip-selector/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/context-help/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/context-help/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button-anchor/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button-anchor/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel-tile/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel-tile/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel/ng-package.json create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.module.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel/public-api.ts create mode 100644 packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts diff --git a/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json b/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json new file mode 100644 index 0000000000..fe3fe5e5ea --- /dev/null +++ b/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add Fv card, chip selector, context help, split button, and summary panel web components", + "packageName": "@ni/ok-components", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts index 5a24f7059d..a04699fc60 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts @@ -4,10 +4,10 @@ import { Component } from '@angular/core'; selector: 'example-fv-accordion-item-section', template: ` - + Calibration assets can expose operator-facing status, location, and ownership details. - + This section starts collapsed to show the default interaction state. diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts new file mode 100644 index 0000000000..9b409658c5 --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-card-section', + template: ` + + + Approved + Updated now + 4 alerts + + + `, + standalone: false +}) +export class FvCardSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts new file mode 100644 index 0000000000..5e9011fbc0 --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-chip-selector-section', + template: ` + + + + `, + standalone: false +}) +export class FvChipSelectorSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts new file mode 100644 index 0000000000..1b6a89cc39 --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-context-help-section', + template: ` + +
+ Support code + +
+
+ `, + standalone: false +}) +export class FvContextHelpSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.component.ts index e8ba70eb13..838077c74f 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.component.ts @@ -4,7 +4,13 @@ import { Component } from '@angular/core'; selector: 'example-fv-section', template: ` + + + + + + `, standalone: false }) diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.module.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.module.ts index 29784c56bb..1fa48b3dba 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.module.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-section.module.ts @@ -1,14 +1,51 @@ import { NgModule } from '@angular/core'; +import { NimbleMenuItemModule, NimbleMenuModule } from '@ni/nimble-angular'; import { OkFvAccordionItemModule } from '@ni/ok-angular/fv/accordion-item'; +import { OkFvCardModule } from '@ni/ok-angular/fv/card'; +import { OkFvChipSelectorModule } from '@ni/ok-angular/fv/chip-selector'; +import { OkFvContextHelpModule } from '@ni/ok-angular/fv/context-help'; import { OkFvSearchInputModule } from '@ni/ok-angular/fv/search-input'; +import { OkFvSplitButtonAnchorModule } from '@ni/ok-angular/fv/split-button-anchor'; +import { OkFvSplitButtonModule } from '@ni/ok-angular/fv/split-button'; +import { OkFvSummaryPanelModule } from '@ni/ok-angular/fv/summary-panel'; +import { OkFvSummaryPanelTileModule } from '@ni/ok-angular/fv/summary-panel-tile'; import { FvAccordionItemSectionComponent } from './fv-accordion-item-section.component'; +import { FvCardSectionComponent } from './fv-card-section.component'; +import { FvChipSelectorSectionComponent } from './fv-chip-selector-section.component'; +import { FvContextHelpSectionComponent } from './fv-context-help-section.component'; import { FvSearchInputSectionComponent } from './fv-search-input-section.component'; import { FvSectionComponent } from './fv-section.component'; +import { FvSplitButtonAnchorSectionComponent } from './fv-split-button-anchor-section.component'; +import { FvSplitButtonSectionComponent } from './fv-split-button-section.component'; +import { FvSummaryPanelSectionComponent } from './fv-summary-panel-section.component'; import { SubContainerModule } from '../sub-container/sub-container.module'; @NgModule({ - declarations: [FvSectionComponent, FvAccordionItemSectionComponent, FvSearchInputSectionComponent], - imports: [OkFvAccordionItemModule, OkFvSearchInputModule, SubContainerModule], + declarations: [ + FvSectionComponent, + FvAccordionItemSectionComponent, + FvCardSectionComponent, + FvChipSelectorSectionComponent, + FvContextHelpSectionComponent, + FvSearchInputSectionComponent, + FvSplitButtonSectionComponent, + FvSplitButtonAnchorSectionComponent, + FvSummaryPanelSectionComponent + ], + imports: [ + NimbleMenuModule, + NimbleMenuItemModule, + OkFvAccordionItemModule, + OkFvCardModule, + OkFvChipSelectorModule, + OkFvContextHelpModule, + OkFvSearchInputModule, + OkFvSplitButtonModule, + OkFvSplitButtonAnchorModule, + OkFvSummaryPanelModule, + OkFvSummaryPanelTileModule, + SubContainerModule + ], exports: [FvSectionComponent] }) export class FvSectionModule { } diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts new file mode 100644 index 0000000000..e4262eaa4c --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-split-button-anchor-section', + template: ` + + + + Open item + Open in new tab + Copy link + + + + `, + standalone: false +}) +export class FvSplitButtonAnchorSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts new file mode 100644 index 0000000000..6b54f85318 --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-split-button-section', + template: ` + + + + Open item + Create copy + Archive selection + + + + `, + standalone: false +}) +export class FvSplitButtonSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts new file mode 100644 index 0000000000..a500603bd7 --- /dev/null +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-fv-summary-panel-section', + template: ` + + + + + + + + + `, + standalone: false +}) +export class FvSummaryPanelSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/ok-angular/fv/card/ng-package.json b/packages/angular-workspace/ok-angular/fv/card/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/card/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts new file mode 100644 index 0000000000..5de152555f --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts @@ -0,0 +1,86 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvCard } from '@ni/ok-components/dist/esm/fv/card'; +import { fvCardTag } from '@ni/ok-components/dist/esm/fv/card'; +import { FvCardAppearance, FvCardInteractionMode } from '@ni/ok-components/dist/esm/fv/card/types'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvCard }; +export { fvCardTag }; +export { FvCardAppearance, FvCardInteractionMode }; + +/** + * Directive to provide Angular integration for the card. + */ +@Directive({ + selector: 'ok-fv-card', + standalone: false +}) +export class OkFvCardDirective { + public get title(): string { + return this.elementRef.nativeElement.title; + } + + @Input() + public set title(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'title', value); + } + + public get subtitle(): string { + return this.elementRef.nativeElement.subtitle; + } + + @Input() + public set subtitle(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'subtitle', value); + } + + public get description(): string { + return this.elementRef.nativeElement.description; + } + + @Input() + public set description(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'description', value); + } + + public get appearance(): FvCardAppearance { + return this.elementRef.nativeElement.appearance; + } + + @Input() + public set appearance(value: FvCardAppearance) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearance', value); + } + + public get interactionMode(): FvCardInteractionMode { + return this.elementRef.nativeElement.interactionMode; + } + + @Input() + public set interactionMode(value: FvCardInteractionMode) { + this.renderer.setProperty(this.elementRef.nativeElement, 'interactionMode', value); + } + + public get disabled(): boolean { + return this.elementRef.nativeElement.disabled; + } + + @Input() + public set disabled(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', toBooleanProperty(value)); + } + + public get initials(): string { + return this.elementRef.nativeElement.initials; + } + + @Input() + public set initials(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'initials', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.module.ts b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.module.ts new file mode 100644 index 0000000000..a668f65871 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvCardDirective } from './ok-fv-card.directive'; + +import '@ni/ok-components/dist/esm/fv/card'; + +@NgModule({ + declarations: [OkFvCardDirective], + imports: [CommonModule], + exports: [OkFvCardDirective] +}) +export class OkFvCardModule { } diff --git a/packages/angular-workspace/ok-angular/fv/card/public-api.ts b/packages/angular-workspace/ok-angular/fv/card/public-api.ts new file mode 100644 index 0000000000..65cc037f53 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/card/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-card.directive'; +export * from './ok-fv-card.module'; diff --git a/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts new file mode 100644 index 0000000000..99a0a97f1f --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts @@ -0,0 +1,272 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvCard, FvCardAppearance, FvCardInteractionMode, OkFvCardDirective } from '../ok-fv-card.directive'; +import { OkFvCardModule } from '../ok-fv-card.module'; + +describe('Ok fv card', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvCardModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-card')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('card', { read: OkFvCardDirective }) public directive: OkFvCardDirective; + @ViewChild('card', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvCardDirective; + let nativeElement: FvCard; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvCardModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for title', () => { + expect(directive.title).toBe(''); + expect(nativeElement.title).toBe(''); + }); + + it('has expected defaults for subtitle', () => { + expect(directive.subtitle).toBe(''); + expect(nativeElement.subtitle).toBe(''); + }); + + it('has expected defaults for description', () => { + expect(directive.description).toBe(''); + expect(nativeElement.description).toBe(''); + }); + + it('has expected defaults for appearance', () => { + expect(directive.appearance).toBe(FvCardAppearance.outline); + expect(nativeElement.appearance).toBe(FvCardAppearance.outline); + }); + + it('has expected defaults for interactionMode', () => { + expect(directive.interactionMode).toBe(FvCardInteractionMode.static); + expect(nativeElement.interactionMode).toBe(FvCardInteractionMode.static); + }); + + it('has expected defaults for disabled', () => { + expect(directive.disabled).toBe(false); + expect(nativeElement.disabled).toBe(false); + }); + + it('has expected defaults for initials', () => { + expect(directive.initials).toBe(''); + expect(nativeElement.initials).toBe(''); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('card', { read: OkFvCardDirective }) public directive: OkFvCardDirective; + @ViewChild('card', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvCardDirective; + let nativeElement: FvCard; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvCardModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for title', () => { + expect(directive.title).toBe('Device health'); + expect(nativeElement.title).toBe('Device health'); + }); + + it('will use template string values for subtitle', () => { + expect(directive.subtitle).toBe('Cell A'); + expect(nativeElement.subtitle).toBe('Cell A'); + }); + + it('will use template string values for description', () => { + expect(directive.description).toBe('Track operator-facing status and alerts.'); + expect(nativeElement.description).toBe('Track operator-facing status and alerts.'); + }); + + it('will use template string values for appearance', () => { + expect(directive.appearance).toBe(FvCardAppearance.block); + expect(nativeElement.appearance).toBe(FvCardAppearance.block); + }); + + it('will use template string values for interactionMode', () => { + expect(directive.interactionMode).toBe(FvCardInteractionMode.card); + expect(nativeElement.interactionMode).toBe(FvCardInteractionMode.card); + }); + + it('will use template string values for disabled', () => { + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('will use template string values for initials', () => { + expect(directive.initials).toBe('DH'); + expect(nativeElement.initials).toBe('DH'); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('card', { read: OkFvCardDirective }) public directive: OkFvCardDirective; + @ViewChild('card', { read: ElementRef }) public elementRef: ElementRef; + public title = 'Device health'; + public subtitle = 'Cell A'; + public description = 'Track operator-facing status and alerts.'; + public appearance: FvCardAppearance = FvCardAppearance.outline; + public interactionMode: FvCardInteractionMode = FvCardInteractionMode.static; + public disabled = false; + public initials = 'DH'; + } + + let fixture: ComponentFixture; + let directive: OkFvCardDirective; + let nativeElement: FvCard; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvCardModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for title', () => { + expect(directive.title).toBe('Device health'); + expect(nativeElement.title).toBe('Device health'); + + fixture.componentInstance.title = 'Operator queue'; + fixture.detectChanges(); + + expect(directive.title).toBe('Operator queue'); + expect(nativeElement.title).toBe('Operator queue'); + }); + + it('can be configured with property binding for subtitle', () => { + expect(directive.subtitle).toBe('Cell A'); + expect(nativeElement.subtitle).toBe('Cell A'); + + fixture.componentInstance.subtitle = 'Cell B'; + fixture.detectChanges(); + + expect(directive.subtitle).toBe('Cell B'); + expect(nativeElement.subtitle).toBe('Cell B'); + }); + + it('can be configured with property binding for description', () => { + expect(directive.description).toBe('Track operator-facing status and alerts.'); + expect(nativeElement.description).toBe('Track operator-facing status and alerts.'); + + fixture.componentInstance.description = 'Review open work items for the next shift.'; + fixture.detectChanges(); + + expect(directive.description).toBe('Review open work items for the next shift.'); + expect(nativeElement.description).toBe('Review open work items for the next shift.'); + }); + + it('can be configured with property binding for appearance', () => { + expect(directive.appearance).toBe(FvCardAppearance.outline); + expect(nativeElement.appearance).toBe(FvCardAppearance.outline); + + fixture.componentInstance.appearance = FvCardAppearance.block; + fixture.detectChanges(); + + expect(directive.appearance).toBe(FvCardAppearance.block); + expect(nativeElement.appearance).toBe(FvCardAppearance.block); + }); + + it('can be configured with property binding for interactionMode', () => { + expect(directive.interactionMode).toBe(FvCardInteractionMode.static); + expect(nativeElement.interactionMode).toBe(FvCardInteractionMode.static); + + fixture.componentInstance.interactionMode = FvCardInteractionMode.card; + fixture.detectChanges(); + + expect(directive.interactionMode).toBe(FvCardInteractionMode.card); + expect(nativeElement.interactionMode).toBe(FvCardInteractionMode.card); + }); + + it('can be configured with property binding for disabled', () => { + expect(directive.disabled).toBeFalse(); + expect(nativeElement.disabled).toBeFalse(); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('can be configured with property binding for initials', () => { + expect(directive.initials).toBe('DH'); + expect(nativeElement.initials).toBe('DH'); + + fixture.componentInstance.initials = 'OP'; + fixture.detectChanges(); + + expect(directive.initials).toBe('OP'); + expect(nativeElement.initials).toBe('OP'); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/ng-package.json b/packages/angular-workspace/ok-angular/fv/chip-selector/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts new file mode 100644 index 0000000000..e23056e895 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts @@ -0,0 +1,84 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvChipSelector } from '@ni/ok-components/dist/esm/fv/chip-selector'; +import { fvChipSelectorTag } from '@ni/ok-components/dist/esm/fv/chip-selector'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvChipSelector }; +export { fvChipSelectorTag }; + +/** + * Directive to provide Angular integration for the chip selector. + */ +@Directive({ + selector: 'ok-fv-chip-selector', + standalone: false +}) +export class OkFvChipSelectorDirective { + public get disabled(): boolean { + return this.elementRef.nativeElement.disabled; + } + + @Input() + public set disabled(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', toBooleanProperty(value)); + } + + public get open(): boolean { + return this.elementRef.nativeElement.open; + } + + @Input() + public set open(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'open', toBooleanProperty(value)); + } + + public get label(): string { + return this.elementRef.nativeElement.label; + } + + @Input() + public set label(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'label', value); + } + + public get selectedValues(): string { + return this.elementRef.nativeElement.selectedValues; + } + + @Input() + public set selectedValues(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'selectedValues', value); + } + + public get options(): string { + return this.elementRef.nativeElement.options; + } + + @Input() + public set options(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'options', value); + } + + public get placeholder(): string { + return this.elementRef.nativeElement.placeholder; + } + + @Input() + public set placeholder(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'placeholder', value); + } + + public get allowCustomValues(): boolean { + return this.elementRef.nativeElement.allowCustomValues; + } + + @Input() + public set allowCustomValues(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'allowCustomValues', toBooleanProperty(value)); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.module.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.module.ts new file mode 100644 index 0000000000..f0ad848df0 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvChipSelectorDirective } from './ok-fv-chip-selector.directive'; + +import '@ni/ok-components/dist/esm/fv/chip-selector'; + +@NgModule({ + declarations: [OkFvChipSelectorDirective], + imports: [CommonModule], + exports: [OkFvChipSelectorDirective] +}) +export class OkFvChipSelectorModule { } diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/public-api.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/public-api.ts new file mode 100644 index 0000000000..92d5a5fdea --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-chip-selector.directive'; +export * from './ok-fv-chip-selector.module'; diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts new file mode 100644 index 0000000000..0329db41b2 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts @@ -0,0 +1,266 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvChipSelector, OkFvChipSelectorDirective } from '../ok-fv-chip-selector.directive'; +import { OkFvChipSelectorModule } from '../ok-fv-chip-selector.module'; + +describe('Ok fv chip selector', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvChipSelectorModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-chip-selector')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('chipSelector', { read: OkFvChipSelectorDirective }) public directive: OkFvChipSelectorDirective; + @ViewChild('chipSelector', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvChipSelectorDirective; + let nativeElement: FvChipSelector; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvChipSelectorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for disabled', () => { + expect(directive.disabled).toBe(false); + expect(nativeElement.disabled).toBe(false); + }); + + it('has expected defaults for open', () => { + expect(directive.open).toBe(false); + expect(nativeElement.open).toBe(false); + }); + + it('has expected defaults for label', () => { + expect(directive.label).toBe(''); + expect(nativeElement.label).toBe(''); + }); + + it('has expected defaults for selectedValues', () => { + expect(directive.selectedValues).toBe(''); + expect(nativeElement.selectedValues).toBe(''); + }); + + it('has expected defaults for options', () => { + expect(directive.options).toBe(''); + expect(nativeElement.options).toBe(''); + }); + + it('has expected defaults for placeholder', () => { + expect(directive.placeholder).toBe('Select values'); + expect(nativeElement.placeholder).toBe('Select values'); + }); + + it('has expected defaults for allowCustomValues', () => { + expect(directive.allowCustomValues).toBe(false); + expect(nativeElement.allowCustomValues).toBe(false); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('chipSelector', { read: OkFvChipSelectorDirective }) public directive: OkFvChipSelectorDirective; + @ViewChild('chipSelector', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvChipSelectorDirective; + let nativeElement: FvChipSelector; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvChipSelectorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for disabled', () => { + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('will use template string values for label', () => { + expect(directive.label).toBe('Selected assets'); + expect(nativeElement.label).toBe('Selected assets'); + }); + + it('will use template string values for selectedValues', () => { + expect(directive.selectedValues).toBe('PXI-1'); + expect(nativeElement.selectedValues).toBe('PXI-1'); + }); + + it('will use template string values for options', () => { + expect(directive.options).toBe('PXI-1,DAQ-1'); + expect(nativeElement.options).toBe('PXI-1,DAQ-1'); + }); + + it('will use template string values for placeholder', () => { + expect(directive.placeholder).toBe('Select assets'); + expect(nativeElement.placeholder).toBe('Select assets'); + }); + + it('will use template string values for allowCustomValues', () => { + expect(directive.allowCustomValues).toBeTrue(); + expect(nativeElement.allowCustomValues).toBeTrue(); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('chipSelector', { read: OkFvChipSelectorDirective }) public directive: OkFvChipSelectorDirective; + @ViewChild('chipSelector', { read: ElementRef }) public elementRef: ElementRef; + public disabled = false; + public open = false; + public label = 'Selected assets'; + public selectedValues = 'PXI-1'; + public options = 'PXI-1,DAQ-1'; + public placeholder = 'Select assets'; + public allowCustomValues = false; + } + + let fixture: ComponentFixture; + let directive: OkFvChipSelectorDirective; + let nativeElement: FvChipSelector; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvChipSelectorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for disabled', () => { + expect(directive.disabled).toBeFalse(); + expect(nativeElement.disabled).toBeFalse(); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('can be configured with property binding for open', () => { + expect(directive.open).toBeFalse(); + expect(nativeElement.open).toBeFalse(); + + fixture.componentInstance.open = true; + fixture.detectChanges(); + + expect(directive.open).toBeTrue(); + expect(nativeElement.open).toBeTrue(); + }); + + it('can be configured with property binding for label', () => { + expect(directive.label).toBe('Selected assets'); + expect(nativeElement.label).toBe('Selected assets'); + + fixture.componentInstance.label = 'Selected systems'; + fixture.detectChanges(); + + expect(directive.label).toBe('Selected systems'); + expect(nativeElement.label).toBe('Selected systems'); + }); + + it('can be configured with property binding for selectedValues', () => { + expect(directive.selectedValues).toBe('PXI-1'); + expect(nativeElement.selectedValues).toBe('PXI-1'); + + fixture.componentInstance.selectedValues = 'DAQ-1,PXI-1'; + fixture.detectChanges(); + + expect(directive.selectedValues).toBe('DAQ-1,PXI-1'); + expect(nativeElement.selectedValues).toBe('DAQ-1,PXI-1'); + }); + + it('can be configured with property binding for options', () => { + expect(directive.options).toBe('PXI-1,DAQ-1'); + expect(nativeElement.options).toBe('PXI-1,DAQ-1'); + + fixture.componentInstance.options = 'PXI-1,DAQ-1,DMM-1'; + fixture.detectChanges(); + + expect(directive.options).toBe('PXI-1,DAQ-1,DMM-1'); + expect(nativeElement.options).toBe('PXI-1,DAQ-1,DMM-1'); + }); + + it('can be configured with property binding for placeholder', () => { + expect(directive.placeholder).toBe('Select assets'); + expect(nativeElement.placeholder).toBe('Select assets'); + + fixture.componentInstance.placeholder = 'Choose assets'; + fixture.detectChanges(); + + expect(directive.placeholder).toBe('Choose assets'); + expect(nativeElement.placeholder).toBe('Choose assets'); + }); + + it('can be configured with property binding for allowCustomValues', () => { + expect(directive.allowCustomValues).toBeFalse(); + expect(nativeElement.allowCustomValues).toBeFalse(); + + fixture.componentInstance.allowCustomValues = true; + fixture.detectChanges(); + + expect(directive.allowCustomValues).toBeTrue(); + expect(nativeElement.allowCustomValues).toBeTrue(); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/context-help/ng-package.json b/packages/angular-workspace/ok-angular/fv/context-help/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/context-help/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts new file mode 100644 index 0000000000..39ae4ea01b --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts @@ -0,0 +1,59 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvContextHelp } from '@ni/ok-components/dist/esm/fv/context-help'; +import { fvContextHelpTag } from '@ni/ok-components/dist/esm/fv/context-help'; +import type { FvContextHelpSeverity } from '@ni/ok-components/dist/esm/fv/context-help/types'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvContextHelp }; +export { fvContextHelpTag }; +export type { FvContextHelpSeverity }; + +/** + * Directive to provide Angular integration for the context help. + */ +@Directive({ + selector: 'ok-fv-context-help', + standalone: false +}) +export class OkFvContextHelpDirective { + public get text(): string { + return this.elementRef.nativeElement.text; + } + + @Input() + public set text(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'text', value); + } + + public get triggerLabel(): string { + return this.elementRef.nativeElement.triggerLabel; + } + + @Input() + public set triggerLabel(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'triggerLabel', value); + } + + public get severity(): FvContextHelpSeverity { + return this.elementRef.nativeElement.severity; + } + + @Input() + public set severity(value: FvContextHelpSeverity) { + this.renderer.setProperty(this.elementRef.nativeElement, 'severity', value); + } + + public get iconVisible(): boolean { + return this.elementRef.nativeElement.iconVisible; + } + + @Input() + public set iconVisible(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'iconVisible', toBooleanProperty(value)); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.module.ts b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.module.ts new file mode 100644 index 0000000000..4565a72be1 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvContextHelpDirective } from './ok-fv-context-help.directive'; + +import '@ni/ok-components/dist/esm/fv/context-help'; + +@NgModule({ + declarations: [OkFvContextHelpDirective], + imports: [CommonModule], + exports: [OkFvContextHelpDirective] +}) +export class OkFvContextHelpModule { } diff --git a/packages/angular-workspace/ok-angular/fv/context-help/public-api.ts b/packages/angular-workspace/ok-angular/fv/context-help/public-api.ts new file mode 100644 index 0000000000..165dc8fbb0 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/context-help/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-context-help.directive'; +export * from './ok-fv-context-help.module'; diff --git a/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts new file mode 100644 index 0000000000..ec32f99c7d --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts @@ -0,0 +1,200 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvContextHelp, type FvContextHelpSeverity, OkFvContextHelpDirective } from '../ok-fv-context-help.directive'; +import { OkFvContextHelpModule } from '../ok-fv-context-help.module'; + +describe('Ok fv context help', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvContextHelpModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-context-help')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('contextHelp', { read: OkFvContextHelpDirective }) public directive: OkFvContextHelpDirective; + @ViewChild('contextHelp', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvContextHelpDirective; + let nativeElement: FvContextHelp; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvContextHelpModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for text', () => { + expect(directive.text).toBe(''); + expect(nativeElement.text).toBe(''); + }); + + it('has expected defaults for triggerLabel', () => { + expect(directive.triggerLabel).toBe('Show help'); + expect(nativeElement.triggerLabel).toBe('Show help'); + }); + + it('has expected defaults for iconVisible', () => { + expect(directive.iconVisible).toBe(false); + expect(nativeElement.iconVisible).toBe(false); + }); + + it('has expected defaults for severity', () => { + expect(directive.severity).toBeUndefined(); + expect(nativeElement.severity).toBeUndefined(); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('contextHelp', { read: OkFvContextHelpDirective }) public directive: OkFvContextHelpDirective; + @ViewChild('contextHelp', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvContextHelpDirective; + let nativeElement: FvContextHelp; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvContextHelpModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for text', () => { + expect(directive.text).toBe('Use the code shown on the device label.'); + expect(nativeElement.text).toBe('Use the code shown on the device label.'); + }); + + it('will use template string values for triggerLabel', () => { + expect(directive.triggerLabel).toBe('Show support code help'); + expect(nativeElement.triggerLabel).toBe('Show support code help'); + }); + + it('will use template string values for severity', () => { + expect(directive.severity).toBe('error'); + expect(nativeElement.severity).toBe('error'); + }); + + it('will use template string values for iconVisible', () => { + expect(directive.iconVisible).toBeTrue(); + expect(nativeElement.iconVisible).toBeTrue(); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('contextHelp', { read: OkFvContextHelpDirective }) public directive: OkFvContextHelpDirective; + @ViewChild('contextHelp', { read: ElementRef }) public elementRef: ElementRef; + public text = 'Use the code shown on the device label.'; + public triggerLabel = 'Show support code help'; + public severity: 'information' | 'error' | undefined = 'information'; + public iconVisible = false; + } + + let fixture: ComponentFixture; + let directive: OkFvContextHelpDirective; + let nativeElement: FvContextHelp; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvContextHelpModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for text', () => { + expect(directive.text).toBe('Use the code shown on the device label.'); + expect(nativeElement.text).toBe('Use the code shown on the device label.'); + + fixture.componentInstance.text = 'Reference the inline help when the label is not visible.'; + fixture.detectChanges(); + + expect(directive.text).toBe('Reference the inline help when the label is not visible.'); + expect(nativeElement.text).toBe('Reference the inline help when the label is not visible.'); + }); + + it('can be configured with property binding for triggerLabel', () => { + expect(directive.triggerLabel).toBe('Show support code help'); + expect(nativeElement.triggerLabel).toBe('Show support code help'); + + fixture.componentInstance.triggerLabel = 'Show operator guidance'; + fixture.detectChanges(); + + expect(directive.triggerLabel).toBe('Show operator guidance'); + expect(nativeElement.triggerLabel).toBe('Show operator guidance'); + }); + + it('can be configured with property binding for severity', () => { + expect(directive.severity).toBe('information'); + expect(nativeElement.severity).toBe('information'); + + fixture.componentInstance.severity = 'error'; + fixture.detectChanges(); + + expect(directive.severity).toBe('error'); + expect(nativeElement.severity).toBe('error'); + }); + + it('can be configured with property binding for iconVisible', () => { + expect(directive.iconVisible).toBeFalse(); + expect(nativeElement.iconVisible).toBeFalse(); + + fixture.componentInstance.iconVisible = true; + fixture.detectChanges(); + + expect(directive.iconVisible).toBeTrue(); + expect(nativeElement.iconVisible).toBeTrue(); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts index 78e16975de..f287471222 100644 --- a/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts @@ -16,6 +16,96 @@ describe('Ok Fv Search Input', () => { }); }); + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('searchInput', { read: OkFvSearchInputDirective }) public directive: OkFvSearchInputDirective; + @ViewChild('searchInput', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSearchInputDirective; + let nativeElement: FvSearchInput; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSearchInputModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for appearance', () => { + expect(directive.appearance).toBe(FvSearchInputAppearance.outline); + expect(nativeElement.appearance).toBe(FvSearchInputAppearance.outline); + }); + + it('has expected defaults for placeholder', () => { + expect(directive.placeholder).toBe(''); + expect(nativeElement.placeholder).toBe(''); + }); + + it('has expected defaults for value', () => { + expect(directive.value).toBe(''); + expect(nativeElement.value).toBe(''); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('searchInput', { read: OkFvSearchInputDirective }) public directive: OkFvSearchInputDirective; + @ViewChild('searchInput', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSearchInputDirective; + let nativeElement: FvSearchInput; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSearchInputModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for appearance', () => { + expect(directive.appearance).toBe(FvSearchInputAppearance.frameless); + expect(nativeElement.appearance).toBe(FvSearchInputAppearance.frameless); + }); + + it('will use template string values for placeholder', () => { + expect(directive.placeholder).toBe('Search assets'); + expect(nativeElement.placeholder).toBe('Search assets'); + }); + + it('will use template string values for value', () => { + expect(directive.value).toBe('PXI'); + expect(nativeElement.value).toBe('PXI'); + }); + }); + describe('with property bound values', () => { @Component({ template: ` diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/ng-package.json b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts new file mode 100644 index 0000000000..2f35ff7e1c --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts @@ -0,0 +1,104 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvSplitButtonAnchor } from '@ni/ok-components/dist/esm/fv/split-button-anchor'; +import { fvSplitButtonAnchorTag } from '@ni/ok-components/dist/esm/fv/split-button-anchor'; +import { FvSplitButtonAnchorAppearance, FvSplitButtonAnchorAppearanceVariant } from '@ni/ok-components/dist/esm/fv/split-button-anchor/types'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvSplitButtonAnchor }; +export { fvSplitButtonAnchorTag }; +export { FvSplitButtonAnchorAppearance, FvSplitButtonAnchorAppearanceVariant }; + +/** + * Directive to provide Angular integration for the split button anchor. + */ +@Directive({ + selector: 'ok-fv-split-button-anchor', + standalone: false +}) +export class OkFvSplitButtonAnchorDirective { + public get label(): string { + return this.elementRef.nativeElement.label; + } + + @Input() + public set label(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'label', value); + } + + public get href(): string { + return this.elementRef.nativeElement.href; + } + + @Input() + public set href(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'href', value); + } + + public get target(): string { + return this.elementRef.nativeElement.target; + } + + @Input() + public set target(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'target', value); + } + + public get rel(): string { + return this.elementRef.nativeElement.rel; + } + + @Input() + public set rel(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'rel', value); + } + + public get download(): string { + return this.elementRef.nativeElement.download; + } + + @Input() + public set download(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'download', value); + } + + public get disabled(): boolean { + return this.elementRef.nativeElement.disabled; + } + + @Input() + public set disabled(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', toBooleanProperty(value)); + } + + public get open(): boolean { + return this.elementRef.nativeElement.open; + } + + @Input() + public set open(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'open', toBooleanProperty(value)); + } + + public get appearance(): FvSplitButtonAnchorAppearance { + return this.elementRef.nativeElement.appearance; + } + + @Input() + public set appearance(value: FvSplitButtonAnchorAppearance) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearance', value); + } + + public get appearanceVariant(): FvSplitButtonAnchorAppearanceVariant { + return this.elementRef.nativeElement.appearanceVariant; + } + + @Input() + public set appearanceVariant(value: FvSplitButtonAnchorAppearanceVariant) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearanceVariant', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.module.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.module.ts new file mode 100644 index 0000000000..2ed300c8b2 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvSplitButtonAnchorDirective } from './ok-fv-split-button-anchor.directive'; + +import '@ni/ok-components/dist/esm/fv/split-button-anchor'; + +@NgModule({ + declarations: [OkFvSplitButtonAnchorDirective], + imports: [CommonModule], + exports: [OkFvSplitButtonAnchorDirective] +}) +export class OkFvSplitButtonAnchorModule { } diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/public-api.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/public-api.ts new file mode 100644 index 0000000000..a8686d6394 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-split-button-anchor.directive'; +export * from './ok-fv-split-button-anchor.module'; diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts new file mode 100644 index 0000000000..a4cbb27267 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts @@ -0,0 +1,314 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvSplitButtonAnchor, FvSplitButtonAnchorAppearance, FvSplitButtonAnchorAppearanceVariant, OkFvSplitButtonAnchorDirective } from '../ok-fv-split-button-anchor.directive'; +import { OkFvSplitButtonAnchorModule } from '../ok-fv-split-button-anchor.module'; + +describe('Ok fv split button anchor', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvSplitButtonAnchorModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-split-button-anchor')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButtonAnchor', { read: OkFvSplitButtonAnchorDirective }) public directive: OkFvSplitButtonAnchorDirective; + @ViewChild('splitButtonAnchor', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonAnchorDirective; + let nativeElement: FvSplitButtonAnchor; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonAnchorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for label', () => { + expect(directive.label).toBe('Primary function'); + expect(nativeElement.label).toBe('Primary function'); + }); + + it('has expected defaults for href', () => { + expect(directive.href).toBe(''); + expect(nativeElement.href).toBe(''); + }); + + it('has expected defaults for target', () => { + expect(directive.target).toBe(''); + expect(nativeElement.target).toBe(''); + }); + + it('has expected defaults for rel', () => { + expect(directive.rel).toBe(''); + expect(nativeElement.rel).toBe(''); + }); + + it('has expected defaults for download', () => { + expect(directive.download).toBe(''); + expect(nativeElement.download).toBe(''); + }); + + it('has expected defaults for disabled', () => { + expect(directive.disabled).toBe(false); + expect(nativeElement.disabled).toBe(false); + }); + + it('has expected defaults for open', () => { + expect(directive.open).toBe(false); + expect(nativeElement.open).toBe(false); + }); + + it('has expected defaults for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAnchorAppearance.outline); + expect(nativeElement.appearance).toBe(FvSplitButtonAnchorAppearance.outline); + }); + + it('has expected defaults for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.default); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.default); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButtonAnchor', { read: OkFvSplitButtonAnchorDirective }) public directive: OkFvSplitButtonAnchorDirective; + @ViewChild('splitButtonAnchor', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonAnchorDirective; + let nativeElement: FvSplitButtonAnchor; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonAnchorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for label', () => { + expect(directive.label).toBe('Open report'); + expect(nativeElement.label).toBe('Open report'); + }); + + it('will use template string values for href', () => { + expect(directive.href).toBe('https://example.com/report'); + expect(nativeElement.href).toBe('https://example.com/report'); + }); + + it('will use template string values for target', () => { + expect(directive.target).toBe('_blank'); + expect(nativeElement.target).toBe('_blank'); + }); + + it('will use template string values for rel', () => { + expect(directive.rel).toBe('noopener'); + expect(nativeElement.rel).toBe('noopener'); + }); + + it('will use template string values for download', () => { + expect(directive.download).toBe('report.csv'); + expect(nativeElement.download).toBe('report.csv'); + }); + + it('will use template string values for disabled', () => { + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('will use template string values for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAnchorAppearance.block); + expect(nativeElement.appearance).toBe(FvSplitButtonAnchorAppearance.block); + }); + + it('will use template string values for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.primary); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.primary); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButtonAnchor', { read: OkFvSplitButtonAnchorDirective }) public directive: OkFvSplitButtonAnchorDirective; + @ViewChild('splitButtonAnchor', { read: ElementRef }) public elementRef: ElementRef; + public label = 'Open report'; + public href = 'https://example.com/report'; + public target = '_blank'; + public rel = 'noopener'; + public download = 'report.csv'; + public disabled = false; + public open = false; + public appearance: FvSplitButtonAnchorAppearance = FvSplitButtonAnchorAppearance.outline; + public appearanceVariant: FvSplitButtonAnchorAppearanceVariant = FvSplitButtonAnchorAppearanceVariant.default; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonAnchorDirective; + let nativeElement: FvSplitButtonAnchor; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonAnchorModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for label', () => { + expect(directive.label).toBe('Open report'); + expect(nativeElement.label).toBe('Open report'); + + fixture.componentInstance.label = 'Open dashboard'; + fixture.detectChanges(); + + expect(directive.label).toBe('Open dashboard'); + expect(nativeElement.label).toBe('Open dashboard'); + }); + + it('can be configured with property binding for href', () => { + expect(directive.href).toBe('https://example.com/report'); + expect(nativeElement.href).toBe('https://example.com/report'); + + fixture.componentInstance.href = 'https://example.com/dashboard'; + fixture.detectChanges(); + + expect(directive.href).toBe('https://example.com/dashboard'); + expect(nativeElement.href).toBe('https://example.com/dashboard'); + }); + + it('can be configured with property binding for target', () => { + expect(directive.target).toBe('_blank'); + expect(nativeElement.target).toBe('_blank'); + + fixture.componentInstance.target = '_self'; + fixture.detectChanges(); + + expect(directive.target).toBe('_self'); + expect(nativeElement.target).toBe('_self'); + }); + + it('can be configured with property binding for rel', () => { + expect(directive.rel).toBe('noopener'); + expect(nativeElement.rel).toBe('noopener'); + + fixture.componentInstance.rel = 'noreferrer'; + fixture.detectChanges(); + + expect(directive.rel).toBe('noreferrer'); + expect(nativeElement.rel).toBe('noreferrer'); + }); + + it('can be configured with property binding for download', () => { + expect(directive.download).toBe('report.csv'); + expect(nativeElement.download).toBe('report.csv'); + + fixture.componentInstance.download = 'summary.csv'; + fixture.detectChanges(); + + expect(directive.download).toBe('summary.csv'); + expect(nativeElement.download).toBe('summary.csv'); + }); + + it('can be configured with property binding for disabled', () => { + expect(directive.disabled).toBeFalse(); + expect(nativeElement.disabled).toBeFalse(); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('can be configured with property binding for open', () => { + expect(directive.open).toBeFalse(); + expect(nativeElement.open).toBeFalse(); + + fixture.componentInstance.open = true; + fixture.detectChanges(); + + expect(directive.open).toBeTrue(); + expect(nativeElement.open).toBeTrue(); + }); + + it('can be configured with property binding for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAnchorAppearance.outline); + expect(nativeElement.appearance).toBe(FvSplitButtonAnchorAppearance.outline); + + fixture.componentInstance.appearance = FvSplitButtonAnchorAppearance.block; + fixture.detectChanges(); + + expect(directive.appearance).toBe(FvSplitButtonAnchorAppearance.block); + expect(nativeElement.appearance).toBe(FvSplitButtonAnchorAppearance.block); + }); + + it('can be configured with property binding for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.default); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.default); + + fixture.componentInstance.appearanceVariant = FvSplitButtonAnchorAppearanceVariant.primary; + fixture.detectChanges(); + + expect(directive.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.primary); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAnchorAppearanceVariant.primary); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/split-button/ng-package.json b/packages/angular-workspace/ok-angular/fv/split-button/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts new file mode 100644 index 0000000000..7b28e959a2 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts @@ -0,0 +1,68 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvSplitButton } from '@ni/ok-components/dist/esm/fv/split-button'; +import { fvSplitButtonTag } from '@ni/ok-components/dist/esm/fv/split-button'; +import { FvSplitButtonAppearance, FvSplitButtonAppearanceVariant } from '@ni/ok-components/dist/esm/fv/split-button/types'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvSplitButton }; +export { fvSplitButtonTag }; +export { FvSplitButtonAppearance, FvSplitButtonAppearanceVariant }; + +/** + * Directive to provide Angular integration for the split button. + */ +@Directive({ + selector: 'ok-fv-split-button', + standalone: false +}) +export class OkFvSplitButtonDirective { + public get label(): string { + return this.elementRef.nativeElement.label; + } + + @Input() + public set label(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'label', value); + } + + public get disabled(): boolean { + return this.elementRef.nativeElement.disabled; + } + + @Input() + public set disabled(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', toBooleanProperty(value)); + } + + public get open(): boolean { + return this.elementRef.nativeElement.open; + } + + @Input() + public set open(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'open', toBooleanProperty(value)); + } + + public get appearance(): FvSplitButtonAppearance { + return this.elementRef.nativeElement.appearance; + } + + @Input() + public set appearance(value: FvSplitButtonAppearance) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearance', value); + } + + public get appearanceVariant(): FvSplitButtonAppearanceVariant { + return this.elementRef.nativeElement.appearanceVariant; + } + + @Input() + public set appearanceVariant(value: FvSplitButtonAppearanceVariant) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearanceVariant', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.module.ts b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.module.ts new file mode 100644 index 0000000000..77c84e9f2e --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvSplitButtonDirective } from './ok-fv-split-button.directive'; + +import '@ni/ok-components/dist/esm/fv/split-button'; + +@NgModule({ + declarations: [OkFvSplitButtonDirective], + imports: [CommonModule], + exports: [OkFvSplitButtonDirective] +}) +export class OkFvSplitButtonModule { } diff --git a/packages/angular-workspace/ok-angular/fv/split-button/public-api.ts b/packages/angular-workspace/ok-angular/fv/split-button/public-api.ts new file mode 100644 index 0000000000..93277791ce --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-split-button.directive'; +export * from './ok-fv-split-button.module'; diff --git a/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts new file mode 100644 index 0000000000..907437a0e1 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts @@ -0,0 +1,219 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvSplitButton, FvSplitButtonAppearance, FvSplitButtonAppearanceVariant, OkFvSplitButtonDirective } from '../ok-fv-split-button.directive'; +import { OkFvSplitButtonModule } from '../ok-fv-split-button.module'; + +describe('Ok fv split button', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvSplitButtonModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-split-button')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButton', { read: OkFvSplitButtonDirective }) public directive: OkFvSplitButtonDirective; + @ViewChild('splitButton', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonDirective; + let nativeElement: FvSplitButton; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for label', () => { + expect(directive.label).toBe('Primary function'); + expect(nativeElement.label).toBe('Primary function'); + }); + + it('has expected defaults for disabled', () => { + expect(directive.disabled).toBe(false); + expect(nativeElement.disabled).toBe(false); + }); + + it('has expected defaults for open', () => { + expect(directive.open).toBe(false); + expect(nativeElement.open).toBe(false); + }); + + it('has expected defaults for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAppearance.outline); + expect(nativeElement.appearance).toBe(FvSplitButtonAppearance.outline); + }); + + it('has expected defaults for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.default); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.default); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButton', { read: OkFvSplitButtonDirective }) public directive: OkFvSplitButtonDirective; + @ViewChild('splitButton', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonDirective; + let nativeElement: FvSplitButton; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for label', () => { + expect(directive.label).toBe('Run action'); + expect(nativeElement.label).toBe('Run action'); + }); + + it('will use template string values for disabled', () => { + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('will use template string values for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAppearance.block); + expect(nativeElement.appearance).toBe(FvSplitButtonAppearance.block); + }); + + it('will use template string values for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.primary); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.primary); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('splitButton', { read: OkFvSplitButtonDirective }) public directive: OkFvSplitButtonDirective; + @ViewChild('splitButton', { read: ElementRef }) public elementRef: ElementRef; + public label = 'Run action'; + public disabled = false; + public open = false; + public appearance: FvSplitButtonAppearance = FvSplitButtonAppearance.outline; + public appearanceVariant: FvSplitButtonAppearanceVariant = FvSplitButtonAppearanceVariant.default; + } + + let fixture: ComponentFixture; + let directive: OkFvSplitButtonDirective; + let nativeElement: FvSplitButton; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSplitButtonModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for label', () => { + expect(directive.label).toBe('Run action'); + expect(nativeElement.label).toBe('Run action'); + + fixture.componentInstance.label = 'Queue action'; + fixture.detectChanges(); + + expect(directive.label).toBe('Queue action'); + expect(nativeElement.label).toBe('Queue action'); + }); + + it('can be configured with property binding for disabled', () => { + expect(directive.disabled).toBeFalse(); + expect(nativeElement.disabled).toBeFalse(); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(directive.disabled).toBeTrue(); + expect(nativeElement.disabled).toBeTrue(); + }); + + it('can be configured with property binding for open', () => { + expect(directive.open).toBeFalse(); + expect(nativeElement.open).toBeFalse(); + + fixture.componentInstance.open = true; + fixture.detectChanges(); + + expect(directive.open).toBeTrue(); + expect(nativeElement.open).toBeTrue(); + }); + + it('can be configured with property binding for appearance', () => { + expect(directive.appearance).toBe(FvSplitButtonAppearance.outline); + expect(nativeElement.appearance).toBe(FvSplitButtonAppearance.outline); + + fixture.componentInstance.appearance = FvSplitButtonAppearance.block; + fixture.detectChanges(); + + expect(directive.appearance).toBe(FvSplitButtonAppearance.block); + expect(nativeElement.appearance).toBe(FvSplitButtonAppearance.block); + }); + + it('can be configured with property binding for appearanceVariant', () => { + expect(directive.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.default); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.default); + + fixture.componentInstance.appearanceVariant = FvSplitButtonAppearanceVariant.primary; + fixture.detectChanges(); + + expect(directive.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.primary); + expect(nativeElement.appearanceVariant).toBe(FvSplitButtonAppearanceVariant.primary); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ng-package.json b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts new file mode 100644 index 0000000000..dc8f3df614 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts @@ -0,0 +1,68 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvSummaryPanelTile } from '@ni/ok-components/dist/esm/fv/summary-panel-tile'; +import { fvSummaryPanelTileTag } from '@ni/ok-components/dist/esm/fv/summary-panel-tile'; +import { FvSummaryPanelTileTextPosition } from '@ni/ok-components/dist/esm/fv/summary-panel-tile/types'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvSummaryPanelTile }; +export { fvSummaryPanelTileTag }; +export { FvSummaryPanelTileTextPosition }; + +/** + * Directive to provide Angular integration for the summary panel tile. + */ +@Directive({ + selector: 'ok-fv-summary-panel-tile', + standalone: false +}) +export class OkFvSummaryPanelTileDirective { + public get count(): string { + return this.elementRef.nativeElement.count; + } + + @Input() + public set count(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'count', value); + } + + public get label(): string { + return this.elementRef.nativeElement.label; + } + + @Input() + public set label(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'label', value); + } + + public get legacyStyle(): boolean { + return this.elementRef.nativeElement.legacyStyle; + } + + @Input() + public set legacyStyle(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'legacyStyle', toBooleanProperty(value)); + } + + public get selected(): boolean { + return this.elementRef.nativeElement.selected; + } + + @Input() + public set selected(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'selected', toBooleanProperty(value)); + } + + public get textPosition(): FvSummaryPanelTileTextPosition { + return this.elementRef.nativeElement.textPosition; + } + + @Input() + public set textPosition(value: FvSummaryPanelTileTextPosition) { + this.renderer.setProperty(this.elementRef.nativeElement, 'textPosition', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.module.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.module.ts new file mode 100644 index 0000000000..f58a527243 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvSummaryPanelTileDirective } from './ok-fv-summary-panel-tile.directive'; + +import '@ni/ok-components/dist/esm/fv/summary-panel-tile'; + +@NgModule({ + declarations: [OkFvSummaryPanelTileDirective], + imports: [CommonModule], + exports: [OkFvSummaryPanelTileDirective] +}) +export class OkFvSummaryPanelTileModule { } diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/public-api.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/public-api.ts new file mode 100644 index 0000000000..91727edc6d --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-summary-panel-tile.directive'; +export * from './ok-fv-summary-panel-tile.module'; diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts new file mode 100644 index 0000000000..6668279cf8 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts @@ -0,0 +1,224 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvSummaryPanelTile, FvSummaryPanelTileTextPosition, OkFvSummaryPanelTileDirective } from '../ok-fv-summary-panel-tile.directive'; +import { OkFvSummaryPanelTileModule } from '../ok-fv-summary-panel-tile.module'; + +describe('Ok fv summary panel tile', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvSummaryPanelTileModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-summary-panel-tile')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanelTile', { read: OkFvSummaryPanelTileDirective }) public directive: OkFvSummaryPanelTileDirective; + @ViewChild('summaryPanelTile', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelTileDirective; + let nativeElement: FvSummaryPanelTile; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelTileModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for count', () => { + expect(directive.count).toBe(''); + expect(nativeElement.count).toBe(''); + }); + + it('has expected defaults for label', () => { + expect(directive.label).toBe(''); + expect(nativeElement.label).toBe(''); + }); + + it('has expected defaults for legacyStyle', () => { + expect(directive.legacyStyle).toBe(false); + expect(nativeElement.legacyStyle).toBe(false); + }); + + it('has expected defaults for selected', () => { + expect(directive.selected).toBe(false); + expect(nativeElement.selected).toBe(false); + }); + + it('has expected defaults for textPosition', () => { + expect(directive.textPosition).toBe(FvSummaryPanelTileTextPosition.beside); + expect(nativeElement.textPosition).toBe(FvSummaryPanelTileTextPosition.beside); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanelTile', { read: OkFvSummaryPanelTileDirective }) public directive: OkFvSummaryPanelTileDirective; + @ViewChild('summaryPanelTile', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelTileDirective; + let nativeElement: FvSummaryPanelTile; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelTileModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for count', () => { + expect(directive.count).toBe('7'); + expect(nativeElement.count).toBe('7'); + }); + + it('will use template string values for label', () => { + expect(directive.label).toBe('open items'); + expect(nativeElement.label).toBe('open items'); + }); + + it('will use template string values for legacyStyle', () => { + expect(directive.legacyStyle).toBeTrue(); + expect(nativeElement.legacyStyle).toBeTrue(); + }); + + it('will use template string values for selected', () => { + expect(directive.selected).toBeTrue(); + expect(nativeElement.selected).toBeTrue(); + }); + + it('will use template string values for textPosition', () => { + expect(directive.textPosition).toBe(FvSummaryPanelTileTextPosition.under); + expect(nativeElement.textPosition).toBe(FvSummaryPanelTileTextPosition.under); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanelTile', { read: OkFvSummaryPanelTileDirective }) public directive: OkFvSummaryPanelTileDirective; + @ViewChild('summaryPanelTile', { read: ElementRef }) public elementRef: ElementRef; + public count = '7'; + public label = 'open items'; + public legacyStyle = false; + public selected = false; + public textPosition: FvSummaryPanelTileTextPosition = FvSummaryPanelTileTextPosition.beside; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelTileDirective; + let nativeElement: FvSummaryPanelTile; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelTileModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for count', () => { + expect(directive.count).toBe('7'); + expect(nativeElement.count).toBe('7'); + + fixture.componentInstance.count = '12'; + fixture.detectChanges(); + + expect(directive.count).toBe('12'); + expect(nativeElement.count).toBe('12'); + }); + + it('can be configured with property binding for label', () => { + expect(directive.label).toBe('open items'); + expect(nativeElement.label).toBe('open items'); + + fixture.componentInstance.label = 'pending reviews'; + fixture.detectChanges(); + + expect(directive.label).toBe('pending reviews'); + expect(nativeElement.label).toBe('pending reviews'); + }); + + it('can be configured with property binding for legacyStyle', () => { + expect(directive.legacyStyle).toBeFalse(); + expect(nativeElement.legacyStyle).toBeFalse(); + + fixture.componentInstance.legacyStyle = true; + fixture.detectChanges(); + + expect(directive.legacyStyle).toBeTrue(); + expect(nativeElement.legacyStyle).toBeTrue(); + }); + + it('can be configured with property binding for selected', () => { + expect(directive.selected).toBeFalse(); + expect(nativeElement.selected).toBeFalse(); + + fixture.componentInstance.selected = true; + fixture.detectChanges(); + + expect(directive.selected).toBeTrue(); + expect(nativeElement.selected).toBeTrue(); + }); + + it('can be configured with property binding for textPosition', () => { + expect(directive.textPosition).toBe(FvSummaryPanelTileTextPosition.beside); + expect(nativeElement.textPosition).toBe(FvSummaryPanelTileTextPosition.beside); + + fixture.componentInstance.textPosition = FvSummaryPanelTileTextPosition.under; + fixture.detectChanges(); + + expect(directive.textPosition).toBe(FvSummaryPanelTileTextPosition.under); + expect(nativeElement.textPosition).toBe(FvSummaryPanelTileTextPosition.under); + }); + }); +}); diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/ng-package.json b/packages/angular-workspace/ok-angular/fv/summary-panel/ng-package.json new file mode 100644 index 0000000000..90febd7fae --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts new file mode 100644 index 0000000000..24085d6d55 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts @@ -0,0 +1,48 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { FvSummaryPanel } from '@ni/ok-components/dist/esm/fv/summary-panel'; +import { fvSummaryPanelTag } from '@ni/ok-components/dist/esm/fv/summary-panel'; +import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities'; + +export type { FvSummaryPanel }; +export { fvSummaryPanelTag }; + +/** + * Directive to provide Angular integration for the summary panel. + */ +@Directive({ + selector: 'ok-fv-summary-panel', + standalone: false +}) +export class OkFvSummaryPanelDirective { + public get showEditItemsButton(): boolean { + return this.elementRef.nativeElement.showEditItemsButton; + } + + @Input() + public set showEditItemsButton(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'showEditItemsButton', toBooleanProperty(value)); + } + + public get legacyStyle(): boolean { + return this.elementRef.nativeElement.legacyStyle; + } + + @Input() + public set legacyStyle(value: BooleanValueOrAttribute) { + this.renderer.setProperty(this.elementRef.nativeElement, 'legacyStyle', toBooleanProperty(value)); + } + + public get editItemsButtonLabel(): string { + return this.elementRef.nativeElement.editItemsButtonLabel; + } + + @Input() + public set editItemsButtonLabel(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'editItemsButtonLabel', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.module.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.module.ts new file mode 100644 index 0000000000..ef47df520f --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkFvSummaryPanelDirective } from './ok-fv-summary-panel.directive'; + +import '@ni/ok-components/dist/esm/fv/summary-panel'; + +@NgModule({ + declarations: [OkFvSummaryPanelDirective], + imports: [CommonModule], + exports: [OkFvSummaryPanelDirective] +}) +export class OkFvSummaryPanelModule { } diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/public-api.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/public-api.ts new file mode 100644 index 0000000000..f4f0c32f48 --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-fv-summary-panel.directive'; +export * from './ok-fv-summary-panel.module'; diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts new file mode 100644 index 0000000000..818a82a34b --- /dev/null +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts @@ -0,0 +1,176 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type FvSummaryPanel, OkFvSummaryPanelDirective } from '../ok-fv-summary-panel.directive'; +import { OkFvSummaryPanelModule } from '../ok-fv-summary-panel.module'; + +describe('Ok fv summary panel', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkFvSummaryPanelModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-fv-summary-panel')).not.toBeUndefined(); + }); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanel', { read: OkFvSummaryPanelDirective }) public directive: OkFvSummaryPanelDirective; + @ViewChild('summaryPanel', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelDirective; + let nativeElement: FvSummaryPanel; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for showEditItemsButton', () => { + expect(directive.showEditItemsButton).toBe(false); + expect(nativeElement.showEditItemsButton).toBe(false); + }); + + it('has expected defaults for legacyStyle', () => { + expect(directive.legacyStyle).toBe(false); + expect(nativeElement.legacyStyle).toBe(false); + }); + + it('has expected defaults for editItemsButtonLabel', () => { + expect(directive.editItemsButtonLabel).toBe('Configure'); + expect(nativeElement.editItemsButtonLabel).toBe('Configure'); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanel', { read: OkFvSummaryPanelDirective }) public directive: OkFvSummaryPanelDirective; + @ViewChild('summaryPanel', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelDirective; + let nativeElement: FvSummaryPanel; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for showEditItemsButton', () => { + expect(directive.showEditItemsButton).toBeTrue(); + expect(nativeElement.showEditItemsButton).toBeTrue(); + }); + + it('will use template string values for legacyStyle', () => { + expect(directive.legacyStyle).toBeTrue(); + expect(nativeElement.legacyStyle).toBeTrue(); + }); + + it('will use template string values for editItemsButtonLabel', () => { + expect(directive.editItemsButtonLabel).toBe('Customize summary'); + expect(nativeElement.editItemsButtonLabel).toBe('Customize summary'); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + `, + standalone: false + }) + class TestHostComponent { + @ViewChild('summaryPanel', { read: OkFvSummaryPanelDirective }) public directive: OkFvSummaryPanelDirective; + @ViewChild('summaryPanel', { read: ElementRef }) public elementRef: ElementRef; + public showEditItemsButton = false; + public legacyStyle = false; + public editItemsButtonLabel = 'Configure tiles'; + } + + let fixture: ComponentFixture; + let directive: OkFvSummaryPanelDirective; + let nativeElement: FvSummaryPanel; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [OkFvSummaryPanelModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for showEditItemsButton', () => { + expect(directive.showEditItemsButton).toBeFalse(); + expect(nativeElement.showEditItemsButton).toBeFalse(); + + fixture.componentInstance.showEditItemsButton = true; + fixture.detectChanges(); + + expect(directive.showEditItemsButton).toBeTrue(); + expect(nativeElement.showEditItemsButton).toBeTrue(); + }); + + it('can be configured with property binding for legacyStyle', () => { + expect(directive.legacyStyle).toBeFalse(); + expect(nativeElement.legacyStyle).toBeFalse(); + + fixture.componentInstance.legacyStyle = true; + fixture.detectChanges(); + + expect(directive.legacyStyle).toBeTrue(); + expect(nativeElement.legacyStyle).toBeTrue(); + }); + + it('can be configured with property binding for editItemsButtonLabel', () => { + expect(directive.editItemsButtonLabel).toBe('Configure tiles'); + expect(nativeElement.editItemsButtonLabel).toBe('Configure tiles'); + + fixture.componentInstance.editItemsButtonLabel = 'Customize summary'; + fixture.detectChanges(); + + expect(directive.editItemsButtonLabel).toBe('Customize summary'); + expect(nativeElement.editItemsButtonLabel).toBe('Customize summary'); + }); + }); +}); From 5110c62b89f3712c2738faa37baf29ae34c7d137 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 12:50:38 -0500 Subject: [PATCH 02/10] Fix Angular FV lint issues --- .../app/customapp/fv/fv-accordion-item-section.component.ts | 4 ++-- .../src/app/customapp/fv/fv-card-section.component.ts | 5 +++++ .../app/customapp/fv/fv-chip-selector-section.component.ts | 2 ++ .../app/customapp/fv/fv-context-help-section.component.ts | 1 + .../app/customapp/fv/fv-summary-panel-section.component.ts | 2 +- .../context-help/tests/ok-fv-context-help.directive.spec.ts | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts index a04699fc60..2266b6e46a 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts @@ -4,10 +4,10 @@ import { Component } from '@angular/core'; selector: 'example-fv-accordion-item-section', template: ` - + Calibration assets can expose operator-facing status, location, and ownership details. - + This section starts collapsed to show the default interaction state. diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts index 9b409658c5..ae1d30efac 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts @@ -6,10 +6,15 @@ import { Component } from '@angular/core'; Approved Updated now diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts index 5e9011fbc0..eaadefbf09 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts @@ -7,7 +7,9 @@ import { Component } from '@angular/core'; diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts index 1b6a89cc39..a242de2e59 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts @@ -9,6 +9,7 @@ import { Component } from '@angular/core'; diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts index a500603bd7..b5dadf3090 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'example-fv-summary-panel-section', template: ` - + diff --git a/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts index ec32f99c7d..ccc73e5931 100644 --- a/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts @@ -1,6 +1,6 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { type FvContextHelp, type FvContextHelpSeverity, OkFvContextHelpDirective } from '../ok-fv-context-help.directive'; +import { type FvContextHelp, OkFvContextHelpDirective } from '../ok-fv-context-help.directive'; import { OkFvContextHelpModule } from '../ok-fv-context-help.module'; describe('Ok fv context help', () => { From 503acda7854f065d826d082bdc3c0b10f825b401 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:28:24 -0500 Subject: [PATCH 03/10] Address FV Angular review feedback --- ...angular-4d6dfd1a-0b9b-4e15-b821-bac9233fa617.json | 7 +++++++ .../fv/fv-accordion-item-section.component.ts | 4 ++-- .../app/customapp/fv/fv-card-section.component.ts | 7 +------ .../fv/fv-chip-selector-section.component.ts | 4 +--- .../fv/fv-context-help-section.component.ts | 3 +-- .../fv/fv-split-button-section.component.ts | 2 +- .../fv/fv-summary-panel-section.component.ts | 2 +- .../ok-angular/fv/card/ok-fv-card.directive.ts | 4 ++-- .../fv/card/tests/ok-fv-card.directive.spec.ts | 4 ++-- .../chip-selector/ok-fv-chip-selector.directive.ts | 4 ++-- .../tests/ok-fv-chip-selector.directive.spec.ts | 4 ++-- .../fv/context-help/ok-fv-context-help.directive.ts | 4 ++-- .../tests/ok-fv-context-help.directive.spec.ts | 4 ++-- .../tests/ok-fv-search-input.directive.spec.ts | 4 ++-- .../ok-fv-split-button-anchor.directive.ts | 2 +- .../ok-fv-split-button-anchor.directive.spec.ts | 2 +- .../fv/split-button/ok-fv-split-button.directive.ts | 2 +- .../tests/ok-fv-split-button.directive.spec.ts | 2 +- .../ok-fv-summary-panel-tile.directive.ts | 4 ++-- .../tests/ok-fv-summary-panel-tile.directive.spec.ts | 4 ++-- .../summary-panel/ok-fv-summary-panel.directive.ts | 6 +++--- .../tests/ok-fv-summary-panel.directive.spec.ts | 6 +++--- .../eslint-config-nimble/ok/fv/ignore-attributes.js | 12 ++++++++++++ 23 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 change/@ni-ok-angular-4d6dfd1a-0b9b-4e15-b821-bac9233fa617.json diff --git a/change/@ni-ok-angular-4d6dfd1a-0b9b-4e15-b821-bac9233fa617.json b/change/@ni-ok-angular-4d6dfd1a-0b9b-4e15-b821-bac9233fa617.json new file mode 100644 index 0000000000..9fb1ba4941 --- /dev/null +++ b/change/@ni-ok-angular-4d6dfd1a-0b9b-4e15-b821-bac9233fa617.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add FV Angular wrappers for ok components", + "packageName": "@ni/ok-angular", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "patch" +} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts index 2266b6e46a..a04699fc60 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-accordion-item-section.component.ts @@ -4,10 +4,10 @@ import { Component } from '@angular/core'; selector: 'example-fv-accordion-item-section', template: ` - + Calibration assets can expose operator-facing status, location, and ownership details. - + This section starts collapsed to show the default interaction state. diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts index ae1d30efac..f971f4dc2d 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts @@ -6,15 +6,10 @@ import { Component } from '@angular/core'; Approved Updated now diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts index eaadefbf09..a3d7331e03 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-chip-selector-section.component.ts @@ -7,9 +7,7 @@ import { Component } from '@angular/core'; diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts index a242de2e59..4a47243672 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-context-help-section.component.ts @@ -8,8 +8,7 @@ import { Component } from '@angular/core'; Support code diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts index 6b54f85318..5dd41d13e5 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-section.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'example-fv-split-button-section', template: ` - + Open item Create copy diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts index b5dadf3090..2f2ee495c8 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-summary-panel-section.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'example-fv-summary-panel-section', template: ` - + diff --git a/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts index 5de152555f..4040343487 100644 --- a/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/card/ok-fv-card.directive.ts @@ -20,7 +20,7 @@ export class OkFvCardDirective { return this.elementRef.nativeElement.title; } - @Input() + @Input('card-title') public set title(value: string) { this.renderer.setProperty(this.elementRef.nativeElement, 'title', value); } @@ -56,7 +56,7 @@ export class OkFvCardDirective { return this.elementRef.nativeElement.interactionMode; } - @Input() + @Input('interaction-mode') public set interactionMode(value: FvCardInteractionMode) { this.renderer.setProperty(this.elementRef.nativeElement, 'interactionMode', value); } diff --git a/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts index 99a0a97f1f..6e434ae31a 100644 --- a/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts @@ -154,11 +154,11 @@ describe('Ok fv card', () => { @Component({ template: ` diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts index e23056e895..5d72dee286 100644 --- a/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/ok-fv-chip-selector.directive.ts @@ -45,7 +45,7 @@ export class OkFvChipSelectorDirective { return this.elementRef.nativeElement.selectedValues; } - @Input() + @Input('selected-values') public set selectedValues(value: string) { this.renderer.setProperty(this.elementRef.nativeElement, 'selectedValues', value); } @@ -72,7 +72,7 @@ export class OkFvChipSelectorDirective { return this.elementRef.nativeElement.allowCustomValues; } - @Input() + @Input('allow-custom-values') public set allowCustomValues(value: BooleanValueOrAttribute) { this.renderer.setProperty(this.elementRef.nativeElement, 'allowCustomValues', toBooleanProperty(value)); } diff --git a/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts index 0329db41b2..95f6aef919 100644 --- a/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/chip-selector/tests/ok-fv-chip-selector.directive.spec.ts @@ -151,10 +151,10 @@ describe('Ok fv chip selector', () => { [disabled]="disabled" [open]="open" [label]="label" - [selectedValues]="selectedValues" + [selected-values]="selectedValues" [options]="options" [placeholder]="placeholder" - [allowCustomValues]="allowCustomValues"> + [allow-custom-values]="allowCustomValues"> `, standalone: false diff --git a/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts index 39ae4ea01b..4b02f0d003 100644 --- a/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/context-help/ok-fv-context-help.directive.ts @@ -29,7 +29,7 @@ export class OkFvContextHelpDirective { return this.elementRef.nativeElement.triggerLabel; } - @Input() + @Input('trigger-label') public set triggerLabel(value: string) { this.renderer.setProperty(this.elementRef.nativeElement, 'triggerLabel', value); } @@ -47,7 +47,7 @@ export class OkFvContextHelpDirective { return this.elementRef.nativeElement.iconVisible; } - @Input() + @Input('icon-visible') public set iconVisible(value: BooleanValueOrAttribute) { this.renderer.setProperty(this.elementRef.nativeElement, 'iconVisible', toBooleanProperty(value)); } diff --git a/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts index ccc73e5931..a4a62e7d0c 100644 --- a/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/context-help/tests/ok-fv-context-help.directive.spec.ts @@ -122,9 +122,9 @@ describe('Ok fv context help', () => { template: ` + [icon-visible]="iconVisible"> `, standalone: false diff --git a/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts index f287471222..ef2c71ed85 100644 --- a/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/search-input/tests/ok-fv-search-input.directive.spec.ts @@ -49,8 +49,8 @@ describe('Ok Fv Search Input', () => { }); it('has expected defaults for placeholder', () => { - expect(directive.placeholder).toBe(''); - expect(nativeElement.placeholder).toBe(''); + expect(directive.placeholder).toBeUndefined(); + expect(nativeElement.placeholder).toBeUndefined(); }); it('has expected defaults for value', () => { diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts index 2f35ff7e1c..68b35907bf 100644 --- a/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/ok-fv-split-button-anchor.directive.ts @@ -92,7 +92,7 @@ export class OkFvSplitButtonAnchorDirective { return this.elementRef.nativeElement.appearanceVariant; } - @Input() + @Input('appearance-variant') public set appearanceVariant(value: FvSplitButtonAnchorAppearanceVariant) { this.renderer.setProperty(this.elementRef.nativeElement, 'appearanceVariant', value); } diff --git a/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts index a4cbb27267..9eee4611ec 100644 --- a/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/split-button-anchor/tests/ok-fv-split-button-anchor.directive.spec.ts @@ -178,7 +178,7 @@ describe('Ok fv split button anchor', () => { [disabled]="disabled" [open]="open" [appearance]="appearance" - [appearanceVariant]="appearanceVariant"> + [appearance-variant]="appearanceVariant"> `, standalone: false diff --git a/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts index 7b28e959a2..a3e81c847a 100644 --- a/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/split-button/ok-fv-split-button.directive.ts @@ -56,7 +56,7 @@ export class OkFvSplitButtonDirective { return this.elementRef.nativeElement.appearanceVariant; } - @Input() + @Input('appearance-variant') public set appearanceVariant(value: FvSplitButtonAppearanceVariant) { this.renderer.setProperty(this.elementRef.nativeElement, 'appearanceVariant', value); } diff --git a/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts index 907437a0e1..870ba60dec 100644 --- a/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/split-button/tests/ok-fv-split-button.directive.spec.ts @@ -131,7 +131,7 @@ describe('Ok fv split button', () => { [disabled]="disabled" [open]="open" [appearance]="appearance" - [appearanceVariant]="appearanceVariant"> + [appearance-variant]="appearanceVariant"> `, standalone: false diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts index dc8f3df614..3c1ed80fff 100644 --- a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/ok-fv-summary-panel-tile.directive.ts @@ -38,7 +38,7 @@ export class OkFvSummaryPanelTileDirective { return this.elementRef.nativeElement.legacyStyle; } - @Input() + @Input('legacy-style') public set legacyStyle(value: BooleanValueOrAttribute) { this.renderer.setProperty(this.elementRef.nativeElement, 'legacyStyle', toBooleanProperty(value)); } @@ -56,7 +56,7 @@ export class OkFvSummaryPanelTileDirective { return this.elementRef.nativeElement.textPosition; } - @Input() + @Input('text-position') public set textPosition(value: FvSummaryPanelTileTextPosition) { this.renderer.setProperty(this.elementRef.nativeElement, 'textPosition', value); } diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts index 6668279cf8..267d22fdb2 100644 --- a/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/summary-panel-tile/tests/ok-fv-summary-panel-tile.directive.spec.ts @@ -134,9 +134,9 @@ describe('Ok fv summary panel tile', () => { + [text-position]="textPosition"> `, standalone: false diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts index 24085d6d55..3fb8b0f8dd 100644 --- a/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/ok-fv-summary-panel.directive.ts @@ -18,7 +18,7 @@ export class OkFvSummaryPanelDirective { return this.elementRef.nativeElement.showEditItemsButton; } - @Input() + @Input('show-edit-items-button') public set showEditItemsButton(value: BooleanValueOrAttribute) { this.renderer.setProperty(this.elementRef.nativeElement, 'showEditItemsButton', toBooleanProperty(value)); } @@ -27,7 +27,7 @@ export class OkFvSummaryPanelDirective { return this.elementRef.nativeElement.legacyStyle; } - @Input() + @Input('legacy-style') public set legacyStyle(value: BooleanValueOrAttribute) { this.renderer.setProperty(this.elementRef.nativeElement, 'legacyStyle', toBooleanProperty(value)); } @@ -36,7 +36,7 @@ export class OkFvSummaryPanelDirective { return this.elementRef.nativeElement.editItemsButtonLabel; } - @Input() + @Input('edit-items-button-label') public set editItemsButtonLabel(value: string) { this.renderer.setProperty(this.elementRef.nativeElement, 'editItemsButtonLabel', value); } diff --git a/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts index 818a82a34b..4bc8b4c762 100644 --- a/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/summary-panel/tests/ok-fv-summary-panel.directive.spec.ts @@ -110,9 +110,9 @@ describe('Ok fv summary panel', () => { @Component({ template: ` + [show-edit-items-button]="showEditItemsButton" + [legacy-style]="legacyStyle" + [edit-items-button-label]="editItemsButtonLabel"> `, standalone: false diff --git a/packages/eslint-config-nimble/ok/fv/ignore-attributes.js b/packages/eslint-config-nimble/ok/fv/ignore-attributes.js index 5858185bdb..2166e9cee5 100644 --- a/packages/eslint-config-nimble/ok/fv/ignore-attributes.js +++ b/packages/eslint-config-nimble/ok/fv/ignore-attributes.js @@ -4,4 +4,16 @@ // Fv angular attributes that SHOULD be localized in production, but we don't want to localize // in example apps should be added to the following list: export const fvIgnoreAttributes = [ + 'appearance-variant', + 'card-title', + 'count', + 'description', + 'edit-items-button-label', + 'header', + 'initials', + 'interaction-mode', + 'options', + 'selected-values', + 'subtitle', + 'trigger-label', ]; From 04915885645015eca7467a8e4dd99b5354cad9d9 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:28:45 -0500 Subject: [PATCH 04/10] remove change file --- ...ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json diff --git a/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json b/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json deleted file mode 100644 index fe3fe5e5ea..0000000000 --- a/change/@ni-ok-components-956c8d37-d7bb-4229-b7e0-7c514b563e0e.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Add Fv card, chip selector, context help, split button, and summary panel web components", - "packageName": "@ni/ok-components", - "email": "1458528+fredvisser@users.noreply.github.com", - "dependentChangeType": "patch" -} From d1241ecdf8872aede68c899039bd6c058ff78f72 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:29:43 -0500 Subject: [PATCH 05/10] component-review skill updates --- .../src/fv/.github/skills/component-review/SKILL.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ok-components/src/fv/.github/skills/component-review/SKILL.md b/packages/ok-components/src/fv/.github/skills/component-review/SKILL.md index 8a67d14439..ddd639f990 100644 --- a/packages/ok-components/src/fv/.github/skills/component-review/SKILL.md +++ b/packages/ok-components/src/fv/.github/skills/component-review/SKILL.md @@ -1,7 +1,7 @@ --- name: component-review description: This skill should be used when the user asks to "review this component PR", "review this Nimble component", "review this OK component change", "check component best practices", "review Storybook and wrapper changes", or "propose review feedback responses" for component work in this repository. Also use it when updating older OK components to match newer sibling implementations and current repo review standards. -version: 0.3.5 +version: 0.3.6 --- # Component Review @@ -41,6 +41,7 @@ If the review is for a pull request, inspect unresolved review comments and grou Review the PR structure and release metadata as part of the change quality bar: - Check beachball change files for the right change type, a client-impact-focused description, and the GitHub obfuscated email format +- Check that each published package changed in the PR has its own change file; do not treat a component-package change file as covering Angular, React, or Blazor wrapper package changes - If a bumped monorepo package is only a devDependency consumer, check whether `dependentChangeType` should be `none` - Treat oversized or multi-purpose PRs as review feedback when the scope is hard to reason about - Treat vague PR titles or descriptions as review feedback when they obscure client impact, test gaps, or rollout risk @@ -137,7 +138,9 @@ Review wrappers and example apps with repo-specific expectations: - Confirm Angular wrappers re-export public enum-like types when clients need them - Prefer `BooleanValueOrAttribute` plus `toBooleanProperty` for Angular directive boolean inputs so OK wrappers match the Nimble wrapper coercion pattern instead of relying on raw `booleanAttribute` transforms - Confirm wrapper property names and transforms match repo patterns -- Do not add `i18n` markers to example-client-app strings; the example app is not localized +- For Angular wrappers over web component attributes exposed in dash case, require `@Input('dash-case-name')` aliases on the directive setters so Angular clients can bind with the actual public attribute names instead of only camelCase wrapper property names +- In example-client-app templates, use the actual dash-case custom-element attribute names for static attributes; treat camelCase static attributes like `interactionMode` or `selectedValues` as review feedback because they do not match the real HTML attribute contract +- Do not add `i18n` markers to example-client-app strings; the example app is not localized. For FV example-app strings that intentionally skip localization, prefer the shared ignore list in `packages/eslint-config-nimble/ok/fv/ignore-attributes.js` over sprinkling temporary `i18n-*` markers through the examples - Confirm every example app that should demonstrate the component has been updated, not just the first touched app - Treat missing updates in Angular, Blazor, React, or other repo example apps as review feedback when the component is available there - Confirm build-order assumptions when wrappers consume generated `dist/esm` output from components @@ -167,6 +170,8 @@ Use the commands in as the default val When the review covers example app updates, run each relevant example app and confirm the updated component renders and behaves correctly instead of relying on static code inspection alone. +When the review adds or updates wrapper default-value tests, confirm those expectations against the underlying component implementation or inherited base-class behavior instead of assuming empty-string defaults for text-like properties. + Treat "example app was not run" as a testing gap unless environment constraints make runtime validation impossible. If unrelated pre-existing failures block broader validation, say so explicitly and distinguish them from issues introduced by the reviewed change. From a8d5a03c3348b1d70cf0045f66cacaddbc44f992 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:37:34 -0500 Subject: [PATCH 06/10] Tighten FV example layouts and links --- .../src/app/customapp/fv/fv-card-section.component.ts | 3 +++ .../customapp/fv/fv-split-button-anchor-section.component.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts index f971f4dc2d..9e25ee40a0 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-card-section.component.ts @@ -17,6 +17,9 @@ import { Component } from '@angular/core'; `, + styles: [` + ok-fv-card { max-width: 300px; } + `], standalone: false }) export class FvCardSectionComponent {} \ No newline at end of file diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts index e4262eaa4c..9259464c29 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts @@ -6,8 +6,9 @@ import { Component } from '@angular/core'; Open item From e2fa4298746da80524468c9c6ee611b11f66c92d Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:40:05 -0500 Subject: [PATCH 07/10] open in same tab --- .../customapp/fv/fv-split-button-anchor-section.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts index 9259464c29..a8960369b8 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts +++ b/packages/angular-workspace/example-client-app/src/app/customapp/fv/fv-split-button-anchor-section.component.ts @@ -7,12 +7,10 @@ import { Component } from '@angular/core'; Open item - Open in new tab + Open details Copy link From 24ac35f12df69c40de3f770e06edb14ff64cca98 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 14:46:05 -0500 Subject: [PATCH 08/10] Change files --- ...ok-components-f79eb8e1-0a6a-42ca-9be6-b282c8a59251.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@ni-ok-components-f79eb8e1-0a6a-42ca-9be6-b282c8a59251.json diff --git a/change/@ni-ok-components-f79eb8e1-0a6a-42ca-9be6-b282c8a59251.json b/change/@ni-ok-components-f79eb8e1-0a6a-42ca-9be6-b282c8a59251.json new file mode 100644 index 0000000000..bfdd988e95 --- /dev/null +++ b/change/@ni-ok-components-f79eb8e1-0a6a-42ca-9be6-b282c8a59251.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "component-review skill updates", + "packageName": "@ni/ok-components", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "none" +} From a5d578d671e4505abd80f97884b5915227d48bbc Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Mon, 11 May 2026 15:00:05 -0500 Subject: [PATCH 09/10] test fix --- .../ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts index 6e434ae31a..c6c0943fea 100644 --- a/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts +++ b/packages/angular-workspace/ok-angular/fv/card/tests/ok-fv-card.directive.spec.ts @@ -83,7 +83,7 @@ describe('Ok fv card', () => { @Component({ template: ` Date: Mon, 11 May 2026 16:07:15 -0500 Subject: [PATCH 10/10] Refine FV i18n ignore attributes --- .../ok/fv/ignore-attributes.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/eslint-config-nimble/ok/fv/ignore-attributes.js b/packages/eslint-config-nimble/ok/fv/ignore-attributes.js index 2166e9cee5..54ae3473b8 100644 --- a/packages/eslint-config-nimble/ok/fv/ignore-attributes.js +++ b/packages/eslint-config-nimble/ok/fv/ignore-attributes.js @@ -1,19 +1,26 @@ // Fv angular attributes that SHOULD NOT ever be localized need to be added to ignoreAttributeSets // See: https://github.com/ni/javascript-styleguide/blob/main/packages/eslint-config-angular/lib/ok/fv/ignore-attributes.js -// Fv angular attributes that SHOULD be localized in production, but we don't want to localize -// in example apps should be added to the following list: -export const fvIgnoreAttributes = [ - 'appearance-variant', +const exampleAppOnlyIgnoreAttributes = [ + // Should be localized in production, but are intentionally not localized in example apps. 'card-title', - 'count', 'description', 'edit-items-button-label', 'header', - 'initials', - 'interaction-mode', 'options', 'selected-values', 'subtitle', 'trigger-label', ]; + +const temporaryAlwaysIgnoreAttributes = [ + // Should not be localized. Keep these here until they move into the shared ignoreAttributeSets. + 'count', + 'initials', + 'interaction-mode', +]; + +export const fvIgnoreAttributes = [ + ...exampleAppOnlyIgnoreAttributes, + ...temporaryAlwaysIgnoreAttributes, +];