diff --git a/angular-workspace/projects/example-client-app/src/app/app.component.html b/angular-workspace/projects/example-client-app/src/app/app.component.html index 303015226f..eacfdfabb3 100644 --- a/angular-workspace/projects/example-client-app/src/app/app.component.html +++ b/angular-workspace/projects/example-client-app/src/app/app.component.html @@ -1,5 +1,6 @@ +
diff --git a/angular-workspace/projects/example-client-app/src/app/app.module.ts b/angular-workspace/projects/example-client-app/src/app/app.module.ts index 3b92310d7e..a90069d4ec 100644 --- a/angular-workspace/projects/example-client-app/src/app/app.module.ts +++ b/angular-workspace/projects/example-client-app/src/app/app.module.ts @@ -11,6 +11,7 @@ import { NimbleTextAreaModule, NimbleTextFieldModule, NimbleNumberFieldModule, N NimbleAnchorModule, NimbleAnchorButtonModule, NimbleAnchorTabModule, NimbleAnchorTabsModule, NimbleIconCheckModule, NimbleBannerModule, NimbleAnchorMenuItemModule, NimbleAnchorTreeItemModule } from '@ni/nimble-angular'; import { NimbleLabelProviderCoreModule } from '@ni/nimble-angular/label-provider/core'; +import { NimbleLabelProviderRichTextModule } from '@ni/nimble-angular/label-provider/rich-text'; import { NimbleLabelProviderTableModule } from '@ni/nimble-angular/label-provider/table'; import { NimbleMappingTextModule } from '@ni/nimble-angular/mapping/text'; import { NimbleMappingIconModule } from '@ni/nimble-angular/mapping/icon'; @@ -40,6 +41,7 @@ import { HeaderComponent } from './header/header.component'; ReactiveFormsModule, NimbleThemeProviderModule, NimbleLabelProviderCoreModule, + NimbleLabelProviderRichTextModule, NimbleLabelProviderTableModule, NimbleTextAreaModule, NimbleTextFieldModule, diff --git a/angular-workspace/projects/ni/nimble-angular/README.md b/angular-workspace/projects/ni/nimble-angular/README.md index 3aa2b2a247..310577cb71 100644 --- a/angular-workspace/projects/ni/nimble-angular/README.md +++ b/angular-workspace/projects/ni/nimble-angular/README.md @@ -92,12 +92,15 @@ Most user-visible strings displayed by Nimble components are provided by the cli The standard way to use these in Angular (for localized apps using `@angular/localize`) is: 1. Import the label provider module(s) from your app module: - - `NimbleLabelProviderCoreModule` from `@ni/nimble-angular/label-provider/core`: Used for labels for all components besides the table + - `NimbleLabelProviderCoreModule` from `@ni/nimble-angular/label-provider/core`: Used for labels for all components that do not have a dedicated label provider + - `NimbleLabelProviderRichTextModule` from `@ni/nimble-angular/label-provider/rich-text`: Used for labels for the rich text components - `NimbleLabelProviderTableModule` from `@ni/nimble-angular/label-provider/table`: Used for labels for the table (and table sub-components / column types) -2. To use the Nimble-provided strings (which are already declared with `$localize`), use the `NimbleLabelProvider[Core/Table]WithDefaultsDirective`: +2. To use the Nimble-provided strings (which are already declared with `$localize`), use the `NimbleLabelProvider[Core/Table/RichText]WithDefaultsDirective`: ```html + + diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/ng-package.json b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/ng-package.json new file mode 100644 index 0000000000..e5440110fb --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} \ No newline at end of file diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text-with-defaults.directive.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text-with-defaults.directive.ts new file mode 100644 index 0000000000..248005216a --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text-with-defaults.directive.ts @@ -0,0 +1,20 @@ +import { Directive, ElementRef, Renderer2 } from '@angular/core'; +import type { LabelProviderRichText } from '@ni/nimble-components/dist/esm/label-provider/rich-text'; + +import '@angular/localize/init'; + +/** + * Directive for nimble-label-provider-rich-text which will initialize all of the labels with $localize-tagged strings, for apps + * using @angular/localize. + */ +@Directive({ + selector: 'nimble-label-provider-rich-text[withDefaults]' +}) +export class NimbleLabelProviderRichTextWithDefaultsDirective { + public constructor(protected readonly renderer: Renderer2, protected readonly elementRef: ElementRef) { + this.elementRef.nativeElement.toggleBold = $localize`:Nimble rich text - toggle bold|:Bold`; + this.elementRef.nativeElement.toggleItalics = $localize`:Nimble rich text - toggle italics|:Italics`; + this.elementRef.nativeElement.toggleBulletedList = $localize`:Nimble rich text - toggle bulleted list|:Bulleted List`; + this.elementRef.nativeElement.toggleNumberedList = $localize`:Nimble rich text - toggle numbered list|:Numbered List`; + } +} \ No newline at end of file diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.directive.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.directive.ts new file mode 100644 index 0000000000..d230fe361f --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.directive.ts @@ -0,0 +1,56 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { LabelProviderRichText } from '@ni/nimble-components/dist/esm/label-provider/rich-text'; + +export type { LabelProviderRichText }; + +/** + * Directive to provide Angular integration for the nimble-rich-text label provider. + * To use the Nimble-provided strings declared via $localize, instead use NimbleLabelProviderRichTextWithDefaultsDirective. + */ +@Directive({ + selector: 'nimble-label-provider-rich-text' +}) +export class NimbleLabelProviderRichTextDirective { + public constructor(protected readonly renderer: Renderer2, protected readonly elementRef: ElementRef) { + } + + public get toggleBold(): string | undefined { + return this.elementRef.nativeElement.toggleBold; + } + + // Renaming because property should have camel casing, but attribute should not + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('toggle-bold') public set toggleBold(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'toggleBold', value); + } + + public get toggleItalics(): string | undefined { + return this.elementRef.nativeElement.toggleItalics; + } + + // Renaming because property should have camel casing, but attribute should not + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('toggle-italics') public set toggleItalics(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'toggleItalics', value); + } + + public get toggleBulletedList(): string | undefined { + return this.elementRef.nativeElement.toggleBulletedList; + } + + // Renaming because property should have camel casing, but attribute should not + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('toggle-bulleted-list') public set toggleBulletedList(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'toggleBulletedList', value); + } + + public get toggleNumberedList(): string | undefined { + return this.elementRef.nativeElement.toggleNumberedList; + } + + // Renaming because property should have camel casing, but attribute should not + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('toggle-numbered-list') public set toggleNumberedList(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'toggleNumberedList', value); + } +} \ No newline at end of file diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.module.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.module.ts new file mode 100644 index 0000000000..08c3cef9f4 --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/nimble-label-provider-rich-text.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NimbleLabelProviderRichTextDirective } from './nimble-label-provider-rich-text.directive'; +import { NimbleLabelProviderRichTextWithDefaultsDirective } from './nimble-label-provider-rich-text-with-defaults.directive'; + +import '@ni/nimble-components/dist/esm/label-provider/rich-text'; + +@NgModule({ + declarations: [NimbleLabelProviderRichTextDirective, NimbleLabelProviderRichTextWithDefaultsDirective], + imports: [ + CommonModule + ], + exports: [NimbleLabelProviderRichTextDirective, NimbleLabelProviderRichTextWithDefaultsDirective] +}) +export class NimbleLabelProviderRichTextModule { } diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/public-api.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/public-api.ts new file mode 100644 index 0000000000..23f0305680 --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/public-api.ts @@ -0,0 +1,3 @@ +export * from './nimble-label-provider-rich-text.directive'; +export * from './nimble-label-provider-rich-text-with-defaults.directive'; +export * from './nimble-label-provider-rich-text.module'; \ No newline at end of file diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text-with-defaults.directive.spec.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text-with-defaults.directive.spec.ts new file mode 100644 index 0000000000..b773a4babb --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text-with-defaults.directive.spec.ts @@ -0,0 +1,45 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { computeMsgId } from '@angular/compiler'; +import { loadTranslations } from '@angular/localize'; +import type { LabelProviderRichText } from '../nimble-label-provider-rich-text.directive'; +import { NimbleLabelProviderRichTextModule } from '../nimble-label-provider-rich-text.module'; + +describe('Nimble LabelProviderRichText withDefaults directive', () => { + @Component({ + template: ` + + + ` + }) + class TestHostComponent { + @ViewChild('labelProvider', { static: true }) public labelProvider: ElementRef; + } + + let labelProvider: LabelProviderRichText; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [NimbleLabelProviderRichTextModule, CommonModule] + }); + loadTranslations({ + [computeMsgId('Bold', 'Nimble rich text - toggle bold')]: 'Translated Bold', + [computeMsgId('Italics', 'Nimble rich text - toggle italics')]: 'Translated Italics', + [computeMsgId('Bulleted List', 'Nimble rich text - toggle bulleted list')]: 'Translated Bulleted List', + [computeMsgId('Numbered List', 'Nimble rich text - toggle numbered list')]: 'Translated Numbered List', + }); + const fixture = TestBed.createComponent(TestHostComponent); + const testHostComponent = fixture.componentInstance; + labelProvider = testHostComponent.labelProvider.nativeElement; + fixture.detectChanges(); + }); + + it('applies translated values for each label', () => { + expect(labelProvider.toggleBold).toBe('Translated Bold'); + expect(labelProvider.toggleItalics).toBe('Translated Italics'); + expect(labelProvider.toggleBulletedList).toBe('Translated Bulleted List'); + expect(labelProvider.toggleNumberedList).toBe('Translated Numbered List'); + }); +}); diff --git a/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text.directive.spec.ts b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text.directive.spec.ts new file mode 100644 index 0000000000..9f95feae2b --- /dev/null +++ b/angular-workspace/projects/ni/nimble-angular/label-provider/rich-text/tests/nimble-label-provider-rich-text.directive.spec.ts @@ -0,0 +1,283 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { LabelProviderRichText, NimbleLabelProviderRichTextDirective } from '../nimble-label-provider-rich-text.directive'; +import { NimbleLabelProviderRichTextModule } from '../nimble-label-provider-rich-text.module'; + +describe('Nimble Label Provider Rich text', () => { + const boldLabel = 'Bold'; + const italicsLabel = 'Italics'; + const numberedListLabel = 'Numbered List'; + const bulletedListLabel = 'Bulleted List'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NimbleLabelProviderRichTextModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('nimble-label-provider-rich-text')).not.toBeUndefined(); + }); + + describe('with no values in template', () => { + @Component({ + template: ` + + ` + }) + class TestHostComponent { + @ViewChild('labelProvider', { read: NimbleLabelProviderRichTextDirective }) public directive: NimbleLabelProviderRichTextDirective; + @ViewChild('labelProvider', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: NimbleLabelProviderRichTextDirective; + let nativeElement: LabelProviderRichText; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [NimbleLabelProviderRichTextModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('has expected defaults for toggleBold', () => { + expect(directive.toggleBold).toBeUndefined(); + expect(nativeElement.toggleBold).toBeUndefined(); + }); + + it('has expected defaults for toggleItalics', () => { + expect(directive.toggleItalics).toBeUndefined(); + expect(nativeElement.toggleItalics).toBeUndefined(); + }); + + it('has expected defaults for toggleBulletedList', () => { + expect(directive.toggleBulletedList).toBeUndefined(); + expect(nativeElement.toggleBulletedList).toBeUndefined(); + }); + + it('has expected defaults for toggleNumberedList', () => { + expect(directive.toggleNumberedList).toBeUndefined(); + expect(nativeElement.toggleNumberedList).toBeUndefined(); + }); + }); + + describe('with template string values', () => { + @Component({ + template: ` + + + ` + }) + class TestHostComponent { + @ViewChild('labelProvider', { read: NimbleLabelProviderRichTextDirective }) public directive: NimbleLabelProviderRichTextDirective; + @ViewChild('labelProvider', { read: ElementRef }) public elementRef: ElementRef; + } + + let fixture: ComponentFixture; + let directive: NimbleLabelProviderRichTextDirective; + let nativeElement: LabelProviderRichText; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [NimbleLabelProviderRichTextModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('will use template string values for toggleBold', () => { + expect(directive.toggleBold).toBe(boldLabel); + expect(nativeElement.toggleBold).toBe(boldLabel); + }); + + it('will use template string values for toggleItalics', () => { + expect(directive.toggleItalics).toBe(italicsLabel); + expect(nativeElement.toggleItalics).toBe(italicsLabel); + }); + + it('will use template string values for toggleBulletedList', () => { + expect(directive.toggleBulletedList).toBe(bulletedListLabel); + expect(nativeElement.toggleBulletedList).toBe(bulletedListLabel); + }); + + it('will use template string values for toggleNumberedList', () => { + expect(directive.toggleNumberedList).toBe(numberedListLabel); + expect(nativeElement.toggleNumberedList).toBe(numberedListLabel); + }); + }); + + describe('with property bound values', () => { + @Component({ + template: ` + + + ` + }) + class TestHostComponent { + @ViewChild('labelProvider', { read: NimbleLabelProviderRichTextDirective }) public directive: NimbleLabelProviderRichTextDirective; + @ViewChild('labelProvider', { read: ElementRef }) public elementRef: ElementRef; + public toggleBold = boldLabel; + public toggleItalics = italicsLabel; + public toggleBulletedList = bulletedListLabel; + public toggleNumberedList = numberedListLabel; + } + + let fixture: ComponentFixture; + let directive: NimbleLabelProviderRichTextDirective; + let nativeElement: LabelProviderRichText; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [NimbleLabelProviderRichTextModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with property binding for toggleBold', () => { + expect(directive.toggleBold).toBe(boldLabel); + expect(nativeElement.toggleBold).toBe(boldLabel); + + fixture.componentInstance.toggleBold = 'newBoldLabel'; + fixture.detectChanges(); + + expect(directive.toggleBold).toBe('newBoldLabel'); + expect(nativeElement.toggleBold).toBe('newBoldLabel'); + }); + + it('can be configured with property binding for toggleItalics', () => { + expect(directive.toggleItalics).toBe(italicsLabel); + expect(nativeElement.toggleItalics).toBe(italicsLabel); + + fixture.componentInstance.toggleItalics = 'newItalicsLabel'; + fixture.detectChanges(); + + expect(directive.toggleItalics).toBe('newItalicsLabel'); + expect(nativeElement.toggleItalics).toBe('newItalicsLabel'); + }); + + it('can be configured with property binding for toggleBulletedList', () => { + expect(directive.toggleBulletedList).toBe(bulletedListLabel); + expect(nativeElement.toggleBulletedList).toBe(bulletedListLabel); + + fixture.componentInstance.toggleBulletedList = 'newBulletedListLabel'; + fixture.detectChanges(); + + expect(directive.toggleBulletedList).toBe('newBulletedListLabel'); + expect(nativeElement.toggleBulletedList).toBe('newBulletedListLabel'); + }); + + it('can be configured with property binding for toggleNumberedList', () => { + expect(directive.toggleNumberedList).toBe(numberedListLabel); + expect(nativeElement.toggleNumberedList).toBe(numberedListLabel); + + fixture.componentInstance.toggleNumberedList = 'newNumberedListLabel'; + fixture.detectChanges(); + + expect(directive.toggleNumberedList).toBe('newNumberedListLabel'); + expect(nativeElement.toggleNumberedList).toBe('newNumberedListLabel'); + }); + }); + + describe('with attribute bound values', () => { + @Component({ + template: ` + + + ` + }) + class TestHostComponent { + @ViewChild('labelProvider', { read: NimbleLabelProviderRichTextDirective }) public directive: NimbleLabelProviderRichTextDirective; + @ViewChild('labelProvider', { read: ElementRef }) public elementRef: ElementRef; + public toggleBold = boldLabel; + public toggleItalics = italicsLabel; + public toggleBulletedList = bulletedListLabel; + public toggleNumberedList = numberedListLabel; + } + + let fixture: ComponentFixture; + let directive: NimbleLabelProviderRichTextDirective; + let nativeElement: LabelProviderRichText; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestHostComponent], + imports: [NimbleLabelProviderRichTextModule] + }); + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + directive = fixture.componentInstance.directive; + nativeElement = fixture.componentInstance.elementRef.nativeElement; + }); + + it('can be configured with attribute binding for toggleBold', () => { + expect(directive.toggleBold).toBe(boldLabel); + expect(nativeElement.toggleBold).toBe(boldLabel); + + fixture.componentInstance.toggleBold = 'newBoldLabel'; + fixture.detectChanges(); + + expect(directive.toggleBold).toBe('newBoldLabel'); + expect(nativeElement.toggleBold).toBe('newBoldLabel'); + }); + + it('can be configured with attribute binding for toggleItalics', () => { + expect(directive.toggleItalics).toBe(italicsLabel); + expect(nativeElement.toggleItalics).toBe(italicsLabel); + + fixture.componentInstance.toggleItalics = 'newItalicsLabel'; + fixture.detectChanges(); + + expect(directive.toggleItalics).toBe('newItalicsLabel'); + expect(nativeElement.toggleItalics).toBe('newItalicsLabel'); + }); + + it('can be configured with attribute binding for toggleBulletedList', () => { + expect(directive.toggleBulletedList).toBe(bulletedListLabel); + expect(nativeElement.toggleBulletedList).toBe(bulletedListLabel); + + fixture.componentInstance.toggleBulletedList = 'newBulletedListLabel'; + fixture.detectChanges(); + + expect(directive.toggleBulletedList).toBe('newBulletedListLabel'); + expect(nativeElement.toggleBulletedList).toBe('newBulletedListLabel'); + }); + + it('can be configured with attribute binding for toggleNumberedList', () => { + expect(directive.toggleNumberedList).toBe(numberedListLabel); + expect(nativeElement.toggleNumberedList).toBe(numberedListLabel); + + fixture.componentInstance.toggleNumberedList = 'newNumberedListLabel'; + fixture.detectChanges(); + + expect(directive.toggleNumberedList).toBe('newNumberedListLabel'); + expect(nativeElement.toggleNumberedList).toBe('newNumberedListLabel'); + }); + }); +}); diff --git a/change/@ni-nimble-angular-1c21fd5b-5570-420d-908a-574c88d5db06.json b/change/@ni-nimble-angular-1c21fd5b-5570-420d-908a-574c88d5db06.json new file mode 100644 index 0000000000..b926f2f473 --- /dev/null +++ b/change/@ni-nimble-angular-1c21fd5b-5570-420d-908a-574c88d5db06.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Angular Integration for rich text label provider", + "packageName": "@ni/nimble-angular", + "email": "123377167+aagash-ni@users.noreply.github.com", + "dependentChangeType": "patch" +}