diff --git a/.github/workflows/pr-checker.yml b/.github/workflows/pr-checker.yml index 1c4fe10f1ab..ea7dbf00964 100644 --- a/.github/workflows/pr-checker.yml +++ b/.github/workflows/pr-checker.yml @@ -1,71 +1,71 @@ name: Pr Checker on: - pull_request: - types: [edited, synchronize, opened, reopened] + pull_request: + types: [edited, synchronize, opened, reopened] permissions: - contents: read - pull-requests: write + contents: read + pull-requests: write jobs: - pr-open-check: - permissions: - issues: write - pull-requests: write - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Verify Linked Issue - uses: hattan/verify-linked-issue-action@v1.1.5 + pr-open-check: + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - message: 'Thanks a lot for your contribution! But, PR does not seem to be linked to any issues. Please [manually link to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#manually-linking-a-pull-request-or-branch-to-an-issue-using-the-issue-sidebar) or mention it in the description using #.' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Verify Linked Issue + uses: hattan/verify-linked-issue-action@v1.1.5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + message: 'Thanks a lot for your contribution! But, PR does not seem to be linked to any issues. Please [manually link to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#manually-linking-a-pull-request-or-branch-to-an-issue-using-the-issue-sidebar) or mention it in the description using #.' - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'npm' + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' - - name: Install packages - run: | - npm install + - name: Install packages + run: | + npm install - - name: Code Format - id: codeFormat - run: | - npm run format:check + - name: Code Format + id: codeFormat + run: | + npm run format:check - #- name: Unit Test - # id: unitTest - # run: | - # npm run test:headless + #- name: Unit Test + # id: unitTest + # run: | + # npm run test:headless - - name: Add Code Format Fail Comment - if: always() && steps.codeFormat.outcome == 'failure' - uses: thollander/actions-comment-pull-request@v1 - with: - message: | - Thanks a lot for your contribution! But, PR does not seem to fit our code format standards. Please run the 'npm run format' command and commit the changes. + - name: Add Code Format Fail Comment + if: always() && steps.codeFormat.outcome == 'failure' + uses: thollander/actions-comment-pull-request@v1 + with: + message: | + Thanks a lot for your contribution! But, PR does not seem to fit our code format standards. Please run the 'npm run format' command and commit the changes. - - name: Add Unit Test Fail Comment - if: always() && steps.unitTest.outcome == 'failure' - uses: thollander/actions-comment-pull-request@v1 - with: - message: | - Thanks a lot for your contribution! But, Unit tests failed. You can check the unit tests with the command 'npm run test:headless' and commit the changes. + #- name: Add Unit Test Fail Comment + # if: always() && steps.unitTest.outcome == 'failure' + # uses: thollander/actions-comment-pull-request@v1 + # with: + # message: | + # Thanks a lot for your contribution! But, Unit tests failed. You can check the unit tests with the command 'npm run test:headless' and commit the changes. - - name: Add Label - if: ${{ failure() }} - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: 'Resolution: Needs Revision' + # - name: Add Label + # if: ${{ failure() }} + # uses: actions-ecosystem/action-add-labels@v1 + # with: + # labels: 'Resolution: Needs Revision' - - name: Remove Label - uses: actions-ecosystem/action-remove-labels@v1 - if: ${{ success() }} - with: - labels: 'Resolution: Needs Revision' + # - name: Remove Label + # uses: actions-ecosystem/action-remove-labels@v1 + # if: ${{ success() }} + # with: + # labels: 'Resolution: Needs Revision' diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c6f887f51..799a006b458 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,40 @@ # Changelog +## [17.17.0](https://github.com/primefaces/primeng/tree/17.17.0) (2024-05-16) +[Full Changelog](https://github.com/primefaces/primeng/compare/17.16.1...17.17.0) + +**Implemented New Features and Enhancements:** +- PrimeNGConfig | Add csp [\#15560](https://github.com/primefaces/primeng/issues/15560) +- TreeSelect: ng-touched not working & add onFocus, onBlur events [\#15537](https://github.com/primefaces/primeng/issues/15537) +- TreeSelect | lazy support [\#15579](https://github.com/primefaces/primeng/issues/15579) + +**Fixed bugs:** +- Table: Numeric Filter [\#15512](https://github.com/primefaces/primeng/issues/15512) +- Table's select all checkbox shouldn't select the checkbox which are disabled [\#15338](https://github.com/primefaces/primeng/issues/15338) +- InputNumber: Selection lost when not selecting all content from left to right when prefix is enabled on the left. [\#15293](https://github.com/primefaces/primeng/issues/15293) +- Button/p-button and tooltip: Doesn't work correctly [\#15390](https://github.com/primefaces/primeng/issues/15390) +- Button Directive | label input type mismatch [\#15590](https://github.com/primefaces/primeng/issues/15590) +- Tooltip: Uncaught TypeError: Cannot read properties of null (reading 'style') [\#15518](https://github.com/primefaces/primeng/issues/15518) +- Tooltip | Tooltip is out of position after dialog opens [\#15529](https://github.com/primefaces/primeng/issues/15529) +- TreeSelect: Panel does not hide on selected item [\#15539](https://github.com/primefaces/primeng/issues/15539) +- Button | Remove wrapper span elements from icon templates [\#15582](https://github.com/primefaces/primeng/issues/15582) +- InputOtp: Paste function works when readonly set to true [\#15567](https://github.com/primefaces/primeng/issues/15567) +- p-tree: Node which is not selectable shouldn't have focus [\#14822](https://github.com/primefaces/primeng/issues/14822) +- Skeleton size property is no longer a string accepting rem values but instead expects shapes [\#15535](https://github.com/primefaces/primeng/issues/15535) +- Dropdown: attr.id not applied to input element when editable: true [\#15542](https://github.com/primefaces/primeng/issues/15542) +- Tooltip visual issue in 17.16.1 [\#15545](https://github.com/primefaces/primeng/issues/15545) +- Keyboard Trap within Galleria component [\#15546](https://github.com/primefaces/primeng/issues/15546) +- AutoFocus | doesn't work in dialog [\#15524](https://github.com/primefaces/primeng/issues/15524) +- Dialog | tabbing order is broken because of pFocusTrap [\#15482](https://github.com/primefaces/primeng/issues/15482) +- Autocomplete Component: Fix unit test [\#15554](https://github.com/primefaces/primeng/issues/15554) +- Orderlist Unit Test Fix [\#15571](https://github.com/primefaces/primeng/issues/15571) +- InputSwitch Component: Fix unit test [\#15573](https://github.com/primefaces/primeng/issues/15573) +- Autocomplete broken unit tests [\#15596](https://github.com/primefaces/primeng/issues/15596) +- Listbox unit test fix [\#15564](https://github.com/primefaces/primeng/issues/15564) +- Rating: Fix broken unit tests [\#15522](https://github.com/primefaces/primeng/issues/15522) +- Dialog: Fix broken unit test [\#15526](https://github.com/primefaces/primeng/issues/15526) +- Autocomplete Component: Fix unit test [\#15554](https://github.com/primefaces/primeng/issues/15554) +- Dropdown: Fix unit test [\#15511](https://github.com/primefaces/primeng/issues/15511) + ## [17.16.1](https://github.com/primefaces/primeng/tree/17.16.1) (2024-05-09) [Full Changelog](https://github.com/primefaces/primeng/compare/17.16.0...17.16.1) @@ -610,16 +646,22 @@ - TypeError: this.focusedItemInfo.mutate is not a function [\#14119](https://github.com/primefaces/primeng/issues/14119) - Upgrade to Angular 17? [\#14063](https://github.com/primefaces/primeng/issues/14063) + +## ![LTS](https://www.primefaces.org/wp-content/uploads/2020/01/lts-icon-24.png "PrimeNG LTS") [16.9.10-LTS](https://www.npmjs.com/package/primeng/v/16.9.10-lts) (2024-05-16) + +**Fixed bugs:** +- Tooltip visual issue in 17.16.1 [\#15545](https://github.com/primefaces/primeng/issues/15545) + ## ![LTS](https://www.primefaces.org/wp-content/uploads/2020/01/lts-icon-24.png "PrimeNG LTS") [16.9.9-LTS](https://www.npmjs.com/package/primeng/v/16.9.9-lts) (2024-05-10) **Fixed bugs:** -- Calendar: Add additional keyboard support [\#14995](https://github.com/primefaces/primeng/issues/14995) -- Tooltip | Tooltip-option tooltipEvent="focus" does not work on p-button [\#15472](https://github.com/primefaces/primeng/issues/15472) -- Tooltip | tooltipEvent - "focus" | "hover" combined [\#15468](https://github.com/primefaces/primeng/issues/15468) -- Dropdown head is empty if no value is provided [\#14954](https://github.com/primefaces/primeng/issues/14954) -- inputNumber with numeric prefix is not working as expected [\#15311](https://github.com/primefaces/primeng/issues/15311) -- Table | the 'not equal' filter is applied immediately after being selected [\#15283](https://github.com/primefaces/primeng/issues/15283) -- Dialog | tabbing order is broken because of pFocusTrap [\#15482](https://github.com/primefaces/primeng/issues/15482) +- Calendar: Add additional keyboard support [\#14995](https://github.com/primefaces/primeng/issues/14995) +- Tooltip | Tooltip-option tooltipEvent="focus" does not work on p-button [\#15472](https://github.com/primefaces/primeng/issues/15472) +- Tooltip | tooltipEvent - "focus" | "hover" combined [\#15468](https://github.com/primefaces/primeng/issues/15468) +- Dropdown head is empty if no value is provided [\#14954](https://github.com/primefaces/primeng/issues/14954) +- inputNumber with numeric prefix is not working as expected [\#15311](https://github.com/primefaces/primeng/issues/15311) +- Table | the 'not equal' filter is applied immediately after being selected [\#15283](https://github.com/primefaces/primeng/issues/15283) +- Dialog | tabbing order is broken because of pFocusTrap [\#15482](https://github.com/primefaces/primeng/issues/15482) - AutoFocus | doesn't work in dialog [\#15524](https://github.com/primefaces/primeng/issues/15524) - Tooltip | Tooltip is out of position after dialog opens [\#15529](https://github.com/primefaces/primeng/issues/15529) diff --git a/package-lock.json b/package-lock.json index f74db72bb92..410e1cbf4b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@types/node": "^16.18.67", "@types/react": "^18.2.41", "@typescript-eslint/eslint-plugin": "^6.6.0", - "chart.js": "3.3.2", + "chart.js": "4.4.2", "codelyzer": "^0.0.28", "del": "^7.1.0", "domino": "^2.1.6", @@ -3468,6 +3468,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "dev": true + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -6554,10 +6560,16 @@ "dev": true }, "node_modules/chart.js": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", - "integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==", - "dev": true + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "dev": true, + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } }, "node_modules/chokidar": { "version": "3.6.0", @@ -21084,4 +21096,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/app/components/accordion/accordion.spec.ts b/src/app/components/accordion/accordion.spec.ts index 58652aaa707..63dec232e57 100755 --- a/src/app/components/accordion/accordion.spec.ts +++ b/src/app/components/accordion/accordion.spec.ts @@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser'; import { Accordion } from './accordion'; import { AccordionTab } from './accordion'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; @Component({ template: ` @@ -175,15 +175,61 @@ describe('Accordion', () => { expect(secondAccordionTabHeaderEl.className).toContain('p-highlight'); }); - it('should be closed', () => { - fixture.detectChanges(); - - const secondAccordionTabOpenEl = fixture.debugElement.children[0].children[0].children[1].query(By.css('a')).nativeElement; - let spaceEvent = { which: 32, preventDefault() {} }; - secondAccordionTab.onKeydown(spaceEvent as KeyboardEvent); + it('should be toggle on space and enter keydown event', () => { fixture.detectChanges(); const secondAccordionTabHeaderEl = fixture.debugElement.children[0].children[0].children[1].query(By.css('.p-accordion-header')).nativeElement; + expect(secondAccordionTabHeaderEl.className).not.toContain('p-highlight'); + + //toggle when enter is pressed + const spaceEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter' }); + secondAccordionTab.onKeydown(spaceEvent); + expect(secondAccordionTabHeaderEl.className).toContain('p-highlight'); + + //toggle when space is pressed + const keyDownEvent = new KeyboardEvent('keydown', { key: 'Space', code: 'Space' }); + secondAccordionTab.onKeydown(keyDownEvent); + fixture.detectChanges(); + + expect(secondAccordionTabHeaderEl.className).not.toContain('p-highlight'); + }); + + describe('onKeydown', () => { + let firstAccordionTabEl; + let secondAccordionTabEl; + + beforeEach(() => { + firstAccordionTabEl = fixture.debugElement.children[0].children[0].children[0].query(By.css('a')).nativeElement; + secondAccordionTabEl = fixture.debugElement.children[0].children[0].children[1].query(By.css('a')).nativeElement; + }); + + const testKeyBoardEvent = (keyCode, eventTarget, activeTab) => { + fixture.detectChanges(); + + const keyDownEvent = new KeyboardEvent('keydown', { code: keyCode }); + spyOnProperty(keyDownEvent, 'target', 'get').and.returnValue(eventTarget); + + accordion.onKeydown(keyDownEvent); + fixture.detectChanges(); + + expect(document.activeElement).toEqual(activeTab); + }; + + it('ArrowDown should focus on the next tab', () => { + testKeyBoardEvent('ArrowDown', firstAccordionTabEl, secondAccordionTabEl); + }); + + it('ArrowUp should focus on the next tab', () => { + testKeyBoardEvent('ArrowUp', secondAccordionTabEl, firstAccordionTabEl); + }); + + it('Home should focus on the first tab', () => { + testKeyBoardEvent('Home', secondAccordionTabEl, firstAccordionTabEl); + }); + + it('End should focus on the last tab', () => { + testKeyBoardEvent('End', firstAccordionTabEl, secondAccordionTabEl); + }); }); }); diff --git a/src/app/components/api/primengconfig.ts b/src/app/components/api/primengconfig.ts index f9f47913339..f5a806fcc7f 100644 --- a/src/app/components/api/primengconfig.ts +++ b/src/app/components/api/primengconfig.ts @@ -12,6 +12,8 @@ export class PrimeNGConfig { overlayOptions: OverlayOptions = {}; + csp = signal<{ nonce: string | undefined }>({ nonce: undefined }); + filterMatchModeOptions = { text: [FilterMatchMode.STARTS_WITH, FilterMatchMode.CONTAINS, FilterMatchMode.NOT_CONTAINS, FilterMatchMode.ENDS_WITH, FilterMatchMode.EQUALS, FilterMatchMode.NOT_EQUALS], numeric: [FilterMatchMode.EQUALS, FilterMatchMode.NOT_EQUALS, FilterMatchMode.LESS_THAN, FilterMatchMode.LESS_THAN_OR_EQUAL_TO, FilterMatchMode.GREATER_THAN, FilterMatchMode.GREATER_THAN_OR_EQUAL_TO], diff --git a/src/app/components/autocomplete/autocomplete.spec.ts b/src/app/components/autocomplete/autocomplete.spec.ts index 66cdecf59d1..9fdd29e6bd4 100755 --- a/src/app/components/autocomplete/autocomplete.spec.ts +++ b/src/app/components/autocomplete/autocomplete.spec.ts @@ -72,7 +72,7 @@ describe('AutoComplete', () => { fixture.detectChanges(); autocomplete.cd.detectChanges(); - const inputDefaultEl = fixture.debugElement.query(By.css('input')).nativeElement; + const inputDefaultEl = fixture.debugElement.query(By.css('.p-autocomplete-input')).nativeElement; expect(inputDefaultEl.disabled).toEqual(true); fixture.detectChanges(); @@ -82,7 +82,7 @@ describe('AutoComplete', () => { autocomplete.cd.detectChanges(); const inputMultipleEl = fixture.debugElement.query(By.css('ul')).query(By.css('input')); - const multiContainer = fixture.debugElement.query(By.css('ul')); + const multiContainer = fixture.debugElement.query(By.css('div')); expect(inputMultipleEl.properties.disabled).toEqual(true); expect(multiContainer.nativeElement.className).toContain('p-disabled'); }); @@ -92,7 +92,7 @@ describe('AutoComplete', () => { fixture.detectChanges(); autocomplete.cd.detectChanges(); - const autocompleteEl = fixture.debugElement.query(By.css('span')).nativeElement; + const autocompleteEl = fixture.debugElement.query(By.css('.p-autocomplete .p-component')).nativeElement; const dropdownIconEl = fixture.debugElement.query(By.css('.p-autocomplete-dropdown')).nativeElement; expect(autocompleteEl.className).toContain('p-autocomplete-dd'); expect(dropdownIconEl).toBeTruthy(); @@ -106,7 +106,7 @@ describe('AutoComplete', () => { fixture.detectChanges(); autocomplete.cd.detectChanges(); - const autocompleteEl = fixture.debugElement.query(By.css('span')).nativeElement; + const autocompleteEl = fixture.debugElement.query(By.css('.p-autocomplete .p-component')).nativeElement; const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(autocompleteEl.className).toContain('Primeng Rocks!'); expect(inputEl.className).toContain('Primeng Rocks!'); @@ -157,22 +157,18 @@ describe('AutoComplete', () => { let focusValue; autocomplete.onFocus.subscribe((value) => (focusValue = value)); - const inputEl = fixture.debugElement.query(By.css('.p-inputtext.p-component')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.focus(); inputEl.nativeElement.click(); fixture.detectChanges(); - const onKeydownSpy = spyOn(autocomplete, 'onKeyDown').and.callThrough(); - const onKeyupSpy = spyOn(autocomplete, 'onKeyUp').and.callThrough(); const onInputSpy = spyOn(autocomplete, 'onInput').and.callThrough(); const handleSuggestionsChangeSpy = spyOn(autocomplete, 'handleSuggestionsChange').and.callThrough(); const filterBrandsSpy = spyOn(testComponent, 'filterBrands').and.callThrough(); inputEl.nativeElement.value = 'v'; - inputEl.nativeElement.dispatchEvent(new Event('keydown')); inputEl.nativeElement.dispatchEvent(new Event('input')); - inputEl.nativeElement.dispatchEvent(new Event('keyup')); tick(300); fixture.detectChanges(); @@ -181,9 +177,7 @@ describe('AutoComplete', () => { expect(suggestionsEls.length).toEqual(2); expect(testComponent.filteredBrands.length).toEqual(2); expect(autocomplete.suggestions).toEqual(testComponent.filteredBrands); - expect(onKeyupSpy).toHaveBeenCalled(); expect(onInputSpy).toHaveBeenCalled(); - expect(onKeydownSpy).toHaveBeenCalled(); expect(handleSuggestionsChangeSpy).toHaveBeenCalled(); expect(filterBrandsSpy).toHaveBeenCalled(); expect(focusValue).toBeTruthy(); @@ -274,20 +268,18 @@ describe('AutoComplete', () => { it('should not show panel', fakeAsync(() => { fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('.p-inputtext.p-component')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.focus(); inputEl.nativeElement.click(); fixture.detectChanges(); inputEl.nativeElement.value = 'vxc'; - inputEl.nativeElement.dispatchEvent(new Event('keydown')); inputEl.nativeElement.dispatchEvent(new Event('input')); - inputEl.nativeElement.dispatchEvent(new Event('keyup')); tick(300); fixture.detectChanges(); - const suggestionsEls = fixture.debugElement.queryAll(By.css('li')); + const suggestionsEls = fixture.debugElement.queryAll(By.css('.p-autocomplete-item')); expect(autocomplete.suggestions.length).toEqual(0); expect(suggestionsEls.length).toEqual(0); expect(testComponent.filteredBrands.length).toEqual(0); @@ -320,21 +312,20 @@ describe('AutoComplete', () => { flush(); })); - it('should use autoHighlight', fakeAsync(() => { + xit('should use autoHighlight', fakeAsync(() => { + // this logic has not been implemented autocomplete.autoHighlight = true; autocomplete.baseZIndex = 20; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('.p-inputtext.p-component')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.focus(); inputEl.nativeElement.click(); fixture.detectChanges(); inputEl.nativeElement.value = 'v'; - inputEl.nativeElement.dispatchEvent(new Event('keydown')); inputEl.nativeElement.dispatchEvent(new Event('input')); - inputEl.nativeElement.dispatchEvent(new Event('keyup')); tick(300); fixture.detectChanges(); @@ -469,8 +460,7 @@ describe('AutoComplete', () => { tick(300); fixture.detectChanges(); - const panelEl = fixture.debugElement.query(By.css('div')); - expect(panelEl).toBeFalsy(); + expect(autocomplete.overlayVisible).toBeFalsy(); inputEl.nativeElement.value = 'va'; inputEl.nativeElement.dispatchEvent(new Event('keydown')); @@ -479,8 +469,7 @@ describe('AutoComplete', () => { tick(300); fixture.detectChanges(); - const updatedPanelEl = fixture.debugElement.query(By.css('div')); - expect(updatedPanelEl).toBeFalsy(); + expect(autocomplete.overlayVisible).toBeTrue(); flush(); })); @@ -488,7 +477,7 @@ describe('AutoComplete', () => { autocomplete.multiple = true; fixture.detectChanges(); - const spanEl = fixture.debugElement.query(By.css('span')); + const spanEl = fixture.debugElement.query(By.css('div')); const listEl = fixture.debugElement.query(By.css('ul')); expect(spanEl.nativeElement.className).toContain('p-autocomplete-multiple'); expect(listEl.nativeElement.className).toContain('p-autocomplete-multiple-container'); @@ -551,34 +540,30 @@ describe('AutoComplete', () => { flush(); })); - it('should delete item with backspace', fakeAsync(() => { + it('should delete chip with backspace', fakeAsync(() => { autocomplete.multiple = true; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.click(); fixture.detectChanges(); const onOptionSelectSpy = spyOn(autocomplete, 'onOptionSelect').and.callThrough(); inputEl.nativeElement.value = 'v'; - inputEl.nativeElement.dispatchEvent(new Event('keydown')); inputEl.nativeElement.dispatchEvent(new Event('input')); - inputEl.nativeElement.dispatchEvent(new Event('keyup')); tick(300); fixture.detectChanges(); - const firstItemEl = fixture.debugElement.queryAll(By.css('li'))[1].nativeElement; - firstItemEl.click(); + autocomplete.onOptionSelect(new Event('click'), 'Volvo'); fixture.detectChanges(); + expect(autocomplete.value[0]).toEqual('Volvo'); expect(autocomplete.value.length).toEqual(1); expect(onOptionSelectSpy).toHaveBeenCalled(); expect(testComponent.brand).toEqual(autocomplete.value); - let backspaceEvent = new Event('keydown'); - Object.defineProperty(backspaceEvent, 'which', { value: 8 }); - Object.defineProperty(backspaceEvent, 'preventDefault', { value: () => {} }); - autocomplete.onKeyDown(backspaceEvent); + + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'Backspace' })); fixture.detectChanges(); expect(autocomplete.value[0]).toEqual(undefined); @@ -622,7 +607,7 @@ describe('AutoComplete', () => { it('should navigate with arrow keys and select with enter', () => { fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.click(); fixture.detectChanges(); @@ -630,15 +615,8 @@ describe('AutoComplete', () => { const onOptionSelectSpy = spyOn(autocomplete, 'onOptionSelect').and.callThrough(); autocomplete.suggestions = ['Volvo', 'VW']; autocomplete.overlayVisible = true; - let navigateEvent = new Event('keydown'); - Object.defineProperty(navigateEvent, 'which', { value: 40 }); - Object.defineProperty(navigateEvent, 'preventDefault', { value: () => {} }); - autocomplete.onKeyDown(navigateEvent); - - let event = new Event('keydown'); - Object.defineProperty(event, 'which', { value: 13 }); - Object.defineProperty(event, 'preventDefault', { value: () => {} }); - autocomplete.onKeyDown(event); + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'ArrowDown' })); + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'Enter' })); fixture.detectChanges(); expect(autocomplete.value).toEqual('Volvo'); @@ -657,26 +635,19 @@ describe('AutoComplete', () => { const onOptionSelectSpy = spyOn(autocomplete, 'onOptionSelect').and.callThrough(); autocomplete.suggestions = ['Volvo', 'VW']; autocomplete.overlayVisible = true; - - let navigateEvent = new Event('keydown'); - Object.defineProperty(navigateEvent, 'which', { value: 40 }); - Object.defineProperty(navigateEvent, 'preventDefault', { value: () => {} }); - autocomplete.onKeyDown(navigateEvent); - - let event = new Event('keydown'); - Object.defineProperty(event, 'which', { value: 9 }); - Object.defineProperty(event, 'preventDefault', { value: () => {} }); - autocomplete.onKeyDown(event); + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'ArrowDown' })); + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'Tab' })); fixture.detectChanges(); + expect(autocomplete.value).toEqual('Volvo'); expect(onOptionSelectSpy).toHaveBeenCalled(); expect(testComponent.brand).toEqual(autocomplete.value); }); - it('should escape with esc key', () => { + it('should escape with esc key', fakeAsync(() => { fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')); + const inputEl = fixture.debugElement.query(By.css('.p-autocomplete-input')); inputEl.nativeElement.dispatchEvent(new Event('focus')); inputEl.nativeElement.click(); fixture.detectChanges(); @@ -685,16 +656,13 @@ describe('AutoComplete', () => { const hideSpy = spyOn(autocomplete, 'hide').and.callThrough(); autocomplete.suggestions = ['Volvo', 'VW']; autocomplete.overlayVisible = true; - let event = new Event('keydown'); - Object.defineProperty(event, 'which', { value: 27 }); - Object.defineProperty(event, 'preventDefault', { value: () => {} }); - - autocomplete.onKeyDown(event); + autocomplete.onKeyDown(new KeyboardEvent('keydown', { code: 'Escape' })); + tick(300); fixture.detectChanges(); - expect(autocomplete.value).toEqual(null); + expect(autocomplete.value).toEqual(undefined); expect(onOptionSelectSpy).not.toHaveBeenCalled(); expect(hideSpy).toHaveBeenCalled(); expect(autocomplete.overlayVisible).toEqual(false); - }); + })); }); diff --git a/src/app/components/autocomplete/autocomplete.ts b/src/app/components/autocomplete/autocomplete.ts index 0d486488ac7..4a056989555 100755 --- a/src/app/components/autocomplete/autocomplete.ts +++ b/src/app/components/autocomplete/autocomplete.ts @@ -76,7 +76,7 @@ export const AUTOCOMPLETE_VALUE_ACCESSOR: any = { role="combobox" [attr.placeholder]="placeholder" [attr.size]="size" - [maxlength]="maxlength" + [attr.maxlength]="maxlength" [tabindex]="!disabled ? tabindex : -1" [readonly]="readonly" [disabled]="disabled" @@ -150,7 +150,7 @@ export const AUTOCOMPLETE_VALUE_ACCESSOR: any = { [attr.placeholder]="!filled ? placeholder : null" [attr.size]="size" aria-autocomplete="list" - [maxlength]="maxlength" + [attr.maxlength]="maxlength" [tabindex]="!disabled ? tabindex : -1" [readonly]="readonly" [disabled]="disabled" diff --git a/src/app/components/breadcrumb/breadcrumb.spec.ts b/src/app/components/breadcrumb/breadcrumb.spec.ts index d7747f4ef63..c1e257aa211 100755 --- a/src/app/components/breadcrumb/breadcrumb.spec.ts +++ b/src/app/components/breadcrumb/breadcrumb.spec.ts @@ -3,6 +3,7 @@ import { By } from '@angular/platform-browser'; import { Breadcrumb, BreadcrumbModule } from './breadcrumb'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('Breadcrumb', () => { let breadcrumb: Breadcrumb; @@ -10,7 +11,8 @@ describe('Breadcrumb', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([{ path: 'test', component: Breadcrumb }]), NoopAnimationsModule, BreadcrumbModule] + imports: [RouterTestingModule.withRoutes([{ path: 'test', component: Breadcrumb }]), NoopAnimationsModule, BreadcrumbModule], + schemas: [NO_ERRORS_SCHEMA] }); fixture = TestBed.createComponent(Breadcrumb); @@ -20,7 +22,7 @@ describe('Breadcrumb', () => { it('should display by default', () => { fixture.detectChanges(); - const breadcrumbEl = fixture.debugElement.query(By.css('div')).nativeElement; + const breadcrumbEl = fixture.debugElement.query(By.css('nav')).nativeElement; expect(breadcrumbEl).toBeTruthy(); }); @@ -29,7 +31,7 @@ describe('Breadcrumb', () => { breadcrumb.styleClass = 'Primeng ROCKS!'; fixture.detectChanges(); - const breadcrumbEl = fixture.debugElement.query(By.css('div')); + const breadcrumbEl = fixture.debugElement.query(By.css('nav')); expect(breadcrumbEl.nativeElement.className).toContain('Primeng ROCKS!'); expect(breadcrumbEl.styles.height).toEqual('300px'); }); @@ -55,7 +57,7 @@ describe('Breadcrumb', () => { breadcrumb.model = [{ label: 'Squad' }, { label: 'Lionel Messi', url: 'https://en.wikipedia.org/wiki/Lionel_Messi', icon: 'pi pi-external-link' }]; fixture.detectChanges(); - const itemsEl = fixture.debugElement.query(By.css('ul')); + const itemsEl = fixture.debugElement.query(By.css('ol')); expect(itemsEl.children[2].children[0]).toBeTruthy(); expect(itemsEl.children[2].children[0].nativeElement.textContent).toEqual('Squad'); expect(itemsEl.children.length).toEqual(5); @@ -80,7 +82,7 @@ describe('Breadcrumb', () => { fixture.detectChanges(); const itemClickSpy = spyOn(breadcrumb, 'onClick').and.callThrough(); - const squadEl = fixture.debugElement.query(By.css('ul')).children[2].children[0].nativeElement; + const squadEl = fixture.debugElement.query(By.css('ol')).children[2].children[0].nativeElement; squadEl.click(); fixture.detectChanges(); @@ -93,7 +95,7 @@ describe('Breadcrumb', () => { fixture.detectChanges(); const itemClickSpy = spyOn(breadcrumb, 'onClick').and.callThrough(); - const messiEl = fixture.debugElement.query(By.css('ul')).children[4].children[0].nativeElement; + const messiEl = fixture.debugElement.query(By.css('ol')).children[4].children[0].nativeElement; messiEl.click(); fixture.detectChanges(); @@ -109,7 +111,7 @@ describe('Breadcrumb', () => { fixture.detectChanges(); const itemClickSpy = spyOn(breadcrumb, 'onClick').and.callThrough(); - const squadEl = fixture.debugElement.query(By.css('ul')).children[2].children[0].nativeElement; + const squadEl = fixture.debugElement.query(By.css('ol')).children[2].children[0].nativeElement; squadEl.click(); fixture.detectChanges(); @@ -130,7 +132,7 @@ describe('Breadcrumb', () => { fixture.detectChanges(); const itemClickSpy = spyOn(breadcrumb, 'onClick').and.callThrough(); - const squadEl = fixture.debugElement.query(By.css('ul')).children[2].children[0].nativeElement; + const squadEl = fixture.debugElement.query(By.css('ol')).children[2].children[0].nativeElement; squadEl.click(); fixture.detectChanges(); diff --git a/src/app/components/breadcrumb/breadcrumb.ts b/src/app/components/breadcrumb/breadcrumb.ts index 8c13ae7c946..3b416daadb9 100755 --- a/src/app/components/breadcrumb/breadcrumb.ts +++ b/src/app/components/breadcrumb/breadcrumb.ts @@ -34,7 +34,7 @@ import { BreadcrumbItemClickEvent } from './breadcrumb.interface'; [target]="home.target" [attr.title]="home.title" [attr.tabindex]="home.disabled ? null : '0'" - [ariaCurrentWhenActive]="isCurrentUrl(home)" + [attr.ariaCurrentWhenActive]="isCurrentUrl(home)" > @@ -55,7 +55,7 @@ import { BreadcrumbItemClickEvent } from './breadcrumb.interface'; [target]="home.target" [attr.title]="home.title" [attr.tabindex]="home.disabled ? null : '0'" - [ariaCurrentWhenActive]="isCurrentUrl(home)" + [attr.ariaCurrentWhenActive]="isCurrentUrl(home)" [fragment]="home.fragment" [queryParamsHandling]="home.queryParamsHandling" [preserveFragment]="home.preserveFragment" @@ -85,7 +85,7 @@ import { BreadcrumbItemClickEvent } from './breadcrumb.interface'; [target]="item.target" [attr.title]="item.title" [attr.tabindex]="item.disabled ? null : '0'" - [ariaCurrentWhenActive]="isCurrentUrl(item)" + [attr.ariaCurrentWhenActive]="isCurrentUrl(item)" > @@ -115,7 +115,7 @@ import { BreadcrumbItemClickEvent } from './breadcrumb.interface'; [skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state" - [ariaCurrentWhenActive]="isCurrentUrl(item)" + [attr.ariaCurrentWhenActive]="isCurrentUrl(item)" > diff --git a/src/app/components/button/button.interface.ts b/src/app/components/button/button.interface.ts index 3d2d5dcda70..6002d5a0b4f 100644 --- a/src/app/components/button/button.interface.ts +++ b/src/app/components/button/button.interface.ts @@ -1,3 +1,4 @@ +import { NgClass } from '@angular/common'; import { TemplateRef } from '@angular/core'; /** @@ -12,9 +13,19 @@ export interface ButtonTemplates { /** * Custom template of icon. */ - icon(): TemplateRef; + icon(context: { + /** + * Icon class. + */ + class: NgClass; + }): TemplateRef; /** * Custom template of loadingicon. */ - loadingicon(): TemplateRef; + loadingicon(context: { + /** + * Icon class. + */ + class: NgClass; + }): TemplateRef; } diff --git a/src/app/components/button/button.ts b/src/app/components/button/button.ts index 9a2ec2cd700..8e3b45b71c3 100755 --- a/src/app/components/button/button.ts +++ b/src/app/components/button/button.ts @@ -61,7 +61,7 @@ export class ButtonDirective implements AfterViewInit, OnDestroy { * Text of the button. * @group Props */ - @Input() get label(): string { + @Input() get label(): string | undefined { return this._label as string; } set label(val: string) { @@ -350,18 +350,14 @@ export class ButtonDirective implements AfterViewInit, OnDestroy { - + - - - + - - - + {{ label }} {{ badge }} @@ -525,6 +521,7 @@ export class Button implements AfterContentInit { iconClass() { return { + [`p-button-loading-icon pi-spin ${this.loadingIcon ?? ''}`]: this.loading, 'p-button-icon': true, 'p-button-icon-left': this.iconPos === 'left' && this.label, 'p-button-icon-right': this.iconPos === 'right' && this.label, diff --git a/src/app/components/calendar/calendar.ts b/src/app/components/calendar/calendar.ts index 6d0f9d0387f..a874ea955dd 100644 --- a/src/app/components/calendar/calendar.ts +++ b/src/app/components/calendar/calendar.ts @@ -3558,6 +3558,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor { } (this.responsiveStyleElement).innerHTML = innerHTML; + DomHandler.setAttribute(this.responsiveStyleElement, 'nonce', this.config?.csp()?.nonce); } } diff --git a/src/app/components/carousel/carousel.ts b/src/app/components/carousel/carousel.ts index ed10709d1b7..1409301165e 100755 --- a/src/app/components/carousel/carousel.ts +++ b/src/app/components/carousel/carousel.ts @@ -520,6 +520,7 @@ export class Carousel implements AfterContentInit { if (!this.carouselStyle) { this.carouselStyle = this.renderer.createElement('style'); this.carouselStyle.type = 'text/css'; + DomHandler.setAttribute(this.carouselStyle, 'nonce', this.config?.csp()?.nonce); this.renderer.appendChild(this.document.head, this.carouselStyle); } diff --git a/src/app/components/chart/chart.ts b/src/app/components/chart/chart.ts index 7222fec6b5f..3d34a8ab0d1 100755 --- a/src/app/components/chart/chart.ts +++ b/src/app/components/chart/chart.ts @@ -23,7 +23,7 @@ export class UIChart implements AfterViewInit, OnDestroy { * Type of the chart. * @group Props */ - @Input() type: string | undefined; + @Input() type: 'bar' | 'line' | 'scatter' | 'bubble' | 'pie' | 'doughnut' | 'polarArea' | 'radar' | undefined; /** * Array of per-chart plugins to customize the chart behaviour. * @group Props diff --git a/src/app/components/confirmdialog/confirmdialog.ts b/src/app/components/confirmdialog/confirmdialog.ts index 6a803b12141..26d69b673f0 100755 --- a/src/app/components/confirmdialog/confirmdialog.ts +++ b/src/app/components/confirmdialog/confirmdialog.ts @@ -616,6 +616,7 @@ export class ConfirmDialog implements AfterContentInit, OnInit, OnDestroy { } this.styleElement.innerHTML = innerHTML; + DomHandler.setAttribute(this.styleElement, 'nonce', this.config?.csp()?.nonce); } } diff --git a/src/app/components/dialog/dialog.spec.ts b/src/app/components/dialog/dialog.spec.ts index 099d2af2da6..f55d375ba73 100755 --- a/src/app/components/dialog/dialog.spec.ts +++ b/src/app/components/dialog/dialog.spec.ts @@ -332,11 +332,7 @@ describe('Dialog', () => { fixture.detectChanges(); tick(300); - const escapeEvent: any = document.createEvent('CustomEvent'); - escapeEvent.which = 27; - escapeEvent.initEvent('keydown', true, true); - document.dispatchEvent(escapeEvent); - document.dispatchEvent(escapeEvent as KeyboardEvent); + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); fixture.detectChanges(); expect(closeSpy).toHaveBeenCalled(); diff --git a/src/app/components/dialog/dialog.ts b/src/app/components/dialog/dialog.ts index 01bd042f87a..2d8fd9c1ec8 100755 --- a/src/app/components/dialog/dialog.ts +++ b/src/app/components/dialog/dialog.ts @@ -701,6 +701,7 @@ export class Dialog implements AfterContentInit, OnInit, OnDestroy { } this.renderer.setProperty(this.styleElement, 'innerHTML', innerHTML); + DomHandler.setAttribute(this.styleElement, 'nonce', this.config?.csp()?.nonce); } } } diff --git a/src/app/components/dropdown/dropdown.spec.ts b/src/app/components/dropdown/dropdown.spec.ts index 3a710c9b9a5..98f37ba2529 100755 --- a/src/app/components/dropdown/dropdown.spec.ts +++ b/src/app/components/dropdown/dropdown.spec.ts @@ -1,5 +1,5 @@ import { ScrollingModule } from '@angular/cdk/scrolling'; -import { TestBed, ComponentFixture, async } from '@angular/core/testing'; +import { TestBed, ComponentFixture, fakeAsync, flush } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Dropdown, DropdownItem } from './dropdown'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -94,17 +94,15 @@ describe('Dropdown', () => { dropdown = fixture.componentInstance; }); - it('should disabled', () => { + it('should disable', () => { fixture.componentInstance.disabled = true; fixture.componentInstance.editable = true; fixture.detectChanges(); dropdown.cd.detectChanges(); const containerEl = fixture.debugElement.query(By.css('.p-dropdown')).nativeElement; - const hiddenEl = fixture.debugElement.queryAll(By.css('.p-hidden-accessible'))[0].children[0].nativeElement; - const editableInputEl = fixture.debugElement.queryAll(By.css('input'))[1].nativeElement; + const editableInputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(containerEl.className).toContain('p-disabled'); - expect(hiddenEl.disabled).toEqual(true); expect(editableInputEl.disabled).toEqual(true); }); @@ -161,8 +159,8 @@ describe('Dropdown', () => { fixture.detectChanges(); const dropdownPanel = fixture.debugElement.query(By.css('.p-dropdown-panel')); - expect(container.className).toContain('p-dropdown-open'); expect(dropdownPanel).toBeTruthy(); + expect(dropdown.overlayVisible).toBeTrue(); }); it('should close', () => { @@ -234,7 +232,7 @@ describe('Dropdown', () => { expect(items.children[2].nativeElement.className).not.toContain('p-highlight'); }); - it('should filtered', async(() => { + it('should filter', fakeAsync(() => { dropdown.filter = true; dropdown.filterValue = 'n'; fixture.detectChanges(); @@ -253,7 +251,7 @@ describe('Dropdown', () => { fixture.detectChanges(); let items = fixture.debugElement.query(By.css('.p-dropdown-items')); - expect(items.nativeElement.children.length).toEqual(3); + expect(items.nativeElement.children.length).toEqual(5); const filterDiv = fixture.debugElement.query(By.css('.p-dropdown-filter-container')); expect(filterDiv).toBeTruthy(); const filterInputEl = fixture.debugElement.query(By.css('.p-dropdown-filter')); @@ -265,9 +263,10 @@ describe('Dropdown', () => { items = fixture.debugElement.query(By.css('.p-dropdown-items')); expect(items.nativeElement.children.length).toEqual(3); + flush(); })); - it('should filtered and display not found warning', async(() => { + it('should filtered and display not found warning', fakeAsync(() => { dropdown.options = [ { label: 'New York', code: 'NY' }, { label: 'Rome', code: 'RM' }, @@ -296,9 +295,10 @@ describe('Dropdown', () => { expect(items.nativeElement.children.length).toEqual(1); expect(emptyMesage).toBeTruthy(); expect(emptyMesage.nativeElement.textContent).toContain('No results found'); + flush(); })); - it('should open with down and altkey', () => { + it('should open with down key', () => { dropdown.options = [ { name: 'New York', code: 'NY' }, { name: 'Rome', code: 'RM' }, @@ -309,14 +309,8 @@ describe('Dropdown', () => { dropdown.appendTo = 'body'; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.initEvent('keydown', true, true); - keydownEvent.altKey = true; - inputEl.dispatchEvent(new Event('focus')); - inputEl.dispatchEvent(keydownEvent); - inputEl.dispatchEvent(new Event('blur')); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowDown' })); fixture.detectChanges(); const dropdownPanel = fixture.debugElement.query(By.css('.p-dropdown-panel')); @@ -335,58 +329,48 @@ describe('Dropdown', () => { dropdown.appendTo = 'body'; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 32; - keydownEvent.initEvent('keydown', true, true); - keydownEvent.altKey = true; - inputEl.dispatchEvent(keydownEvent); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); //open overlay fixture.detectChanges(); const dropdownPanel = fixture.debugElement.query(By.css('.p-dropdown-panel')); expect(dropdownPanel).toBeTruthy(); expect(dropdown.overlayVisible).toBeTruthy(); - keydownEvent.which = 27; - inputEl.dispatchEvent(keydownEvent); + + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape' })); fixture.detectChanges(); expect(dropdown.overlayVisible).toBeFalsy(); }); - it('should select with down key', () => { - groupFixture.detectChanges(); + it('should select with down key when selectOnFocus = true', () => { + fixture.detectChanges(); - testDropdown.options = [ + dropdown.options = [ { name: 'New York', code: 'NY' }, { name: 'Rome', code: 'RM' }, { name: 'London', code: 'LDN' }, { name: 'Istanbul', code: 'IST' }, { name: 'Paris', code: 'PRS' } ]; - testDropdown.appendTo = document.body; - testDropdown.optionValue = 'code'; - testDropdown.optionLabel = 'name'; - testDropdown.filter = true; - testDropdown.filterValue = 'n'; - groupFixture.detectChanges(); - - groupFixture.debugElement.children[2].nativeElement.click(); - groupFixture.detectChanges(); + dropdown.appendTo = document.body; + dropdown.optionValue = 'code'; + dropdown.optionLabel = 'name'; + dropdown.selectOnFocus = true; + fixture.detectChanges(); - expect(testDropdown.selectedOption.name).toEqual('New York'); - const inputEl = groupFixture.debugElement.children[1].query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.initEvent('keydown', true, true); - keydownEvent.altKey = true; - inputEl.dispatchEvent(keydownEvent); - groupFixture.detectChanges(); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + const downKeyboardEvent = new KeyboardEvent('keydown', { code: 'ArrowDown' }); + inputEl.dispatchEvent(downKeyboardEvent); //open overlay and focus on first item + inputEl.dispatchEvent(downKeyboardEvent); //focus next item + fixture.detectChanges(); - keydownEvent.altKey = false; - inputEl.dispatchEvent(keydownEvent); - groupFixture.detectChanges(); + expect(dropdown.overlayVisible).toBeTrue(); + expect(dropdown.modelValue()).toBe('RM'); - expect(testDropdown.selectedOption.name).toEqual('London'); + inputEl.dispatchEvent(downKeyboardEvent); //focus next item + fixture.detectChanges(); + expect(dropdown.modelValue()).toBe('LDN'); }); it('should select with enter key and close the overlay', () => { @@ -400,22 +384,20 @@ describe('Dropdown', () => { dropdown.appendTo = document.body; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.initEvent('keydown', true, true); - keydownEvent.altKey = true; - inputEl.dispatchEvent(keydownEvent); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowDown' })); // open overlay and focus on first item fixture.detectChanges(); - keydownEvent.which = 13; - inputEl.dispatchEvent(keydownEvent); + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); // select and close overlay fixture.detectChanges(); expect(dropdown.overlayVisible).toBeFalsy(); + expect(dropdown.selectedOption.code).toBe('NY'); }); it('should select with up key', () => { + fixture.detectChanges(); + dropdown.selectOnFocus = true; dropdown.options = [ { name: 'New York', code: 'NY' }, { name: 'Rome', code: 'RM' }, @@ -425,18 +407,18 @@ describe('Dropdown', () => { ]; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 38; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowUp' })); fixture.detectChanges(); - expect(dropdown.selectedOption.name).toEqual('Paris'); + expect(dropdown.value.name).toEqual('Paris'); }); it('should select with up key and skip disabled options', () => { + fixture.detectChanges(); + dropdown.optionDisabled = 'inactive'; + dropdown.selectOnFocus = true; dropdown.options = [ { name: 'New York', code: 'NY' }, { name: 'Rome', code: 'RM' }, @@ -446,14 +428,11 @@ describe('Dropdown', () => { ]; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 38; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowUp' })); fixture.detectChanges(); - expect(dropdown.selectedOption.name).toEqual('London'); + expect(dropdown.value.name).toEqual('London'); }); it('should select with filter', () => { @@ -466,94 +445,82 @@ describe('Dropdown', () => { ]; fixture.detectChanges(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.altKey = true; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); + const inputEl = fixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowDown' })); //open overlay fixture.detectChanges(); - keydownEvent.which = 76; - keydownEvent.keyCode = 76; - keydownEvent.key = 'l'; - inputEl.dispatchEvent(keydownEvent); + inputEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'l' })); // type 'l' key + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); // select field fixture.detectChanges(); expect(dropdown.selectedOption.label).toEqual('London'); }); - it('should groupSelect with down key', () => { + it('should focus on next grouped item with down key', () => { + groupDropdown.selectOnFocus = true; groupFixture.detectChanges(); - const inputEl = groupFixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); + const inputEl = groupFixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + const downKeyboardEvent = new KeyboardEvent('keydown', { code: 'ArrowDown' }); + inputEl.dispatchEvent(downKeyboardEvent); //open overlay + inputEl.dispatchEvent(downKeyboardEvent); //focus on first item groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('Audi'); - inputEl.dispatchEvent(keydownEvent); + expect(groupDropdown.selectedOption.label).toEqual('BMW'); + inputEl.dispatchEvent(downKeyboardEvent); groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('BMW'); - inputEl.dispatchEvent(keydownEvent); - inputEl.dispatchEvent(keydownEvent); + expect(groupDropdown.selectedOption.label).toEqual('Mercedes'); + inputEl.dispatchEvent(downKeyboardEvent); + inputEl.dispatchEvent(downKeyboardEvent); groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('Cadillac'); + expect(groupDropdown.selectedOption.label).toEqual('Ford'); }); it('should groupSelect with up key', () => { groupFixture.detectChanges(); - const inputEl = groupFixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); - inputEl.dispatchEvent(keydownEvent); - inputEl.dispatchEvent(keydownEvent); - inputEl.dispatchEvent(keydownEvent); + const inputEl = groupFixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + const keyDownEvent = new KeyboardEvent('keydown', { code: 'ArrowDown' }); + const keyEnterEvent = new KeyboardEvent('keydown', { code: 'Enter' }); + inputEl.dispatchEvent(keyDownEvent); //open overlay and focus on first item + inputEl.dispatchEvent(keyDownEvent); //move down + inputEl.dispatchEvent(keyDownEvent); //move down + inputEl.dispatchEvent(keyDownEvent); //move down + inputEl.dispatchEvent(keyDownEvent); //move down + inputEl.dispatchEvent(keyEnterEvent); //select groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('Cadillac'); - keydownEvent.which = 38; - inputEl.dispatchEvent(keydownEvent); + expect(groupDropdown.selectedOption.label).toEqual('Ford'); + + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowUp' })); //open overlay + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'ArrowUp' })); // move up + inputEl.dispatchEvent(keyEnterEvent); //select groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('Mercedes'); - inputEl.dispatchEvent(keydownEvent); + expect(groupDropdown.selectedOption.label).toEqual('Cadillac'); + inputEl.dispatchEvent(keyDownEvent); //open overlay + inputEl.dispatchEvent(keyDownEvent); //move down + inputEl.dispatchEvent(keyEnterEvent); //select groupFixture.detectChanges(); - expect(groupDropdown.selectedOption.label).toEqual('BMW'); + expect(groupDropdown.selectedOption.label).toEqual('Ford'); }); it('should groupSelect with filter', () => { groupFixture.detectChanges(); - const inputEl = groupFixture.debugElement.query(By.css('input')).nativeElement; - const keydownEvent: any = document.createEvent('CustomEvent'); - keydownEvent.which = 40; - keydownEvent.altKey = true; - keydownEvent.initEvent('keydown', true, true); - inputEl.dispatchEvent(keydownEvent); + const inputEl = groupFixture.debugElement.query(By.css('.p-inputtext')).nativeElement; + const downKeyboardEvent = new KeyboardEvent('keydown', { code: 'ArrowDown' }); + inputEl.dispatchEvent(downKeyboardEvent); //open overlay groupFixture.detectChanges(); - keydownEvent.which = 77; - keydownEvent.keyCode = 77; - keydownEvent.key = 'm'; - keydownEvent.altKey = false; - inputEl.dispatchEvent(keydownEvent); - - expect(groupDropdown.selectedOption.label).toEqual('Mercedes'); - }); - - it('should alternateGroup auto select with alternate children field', () => { + inputEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'm' })); // type 'm' key + inputEl.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); // select field groupFixture.detectChanges(); - expect(alternateGroupDropdown.selectedOption.label).toEqual('Audi'); + expect(groupDropdown.selectedOption.label).toEqual('Mercedes'); }); [null, undefined, ''].map((value) => @@ -561,7 +528,7 @@ describe('Dropdown', () => { dropdown.value = value; fixture.detectChanges(); - expect(dropdown.filled).toBeFalsy(); + expect(dropdown.filled()).toBeFalsy(); }) ); }); diff --git a/src/app/components/dropdown/dropdown.ts b/src/app/components/dropdown/dropdown.ts index 3594b81f369..41408bfbd7b 100755 --- a/src/app/components/dropdown/dropdown.ts +++ b/src/app/components/dropdown/dropdown.ts @@ -169,6 +169,7 @@ export class DropdownItem { *ngIf="editable" #editableInput type="text" + [attr.id]="inputId" [attr.maxlength]="maxlength" [ngClass]="inputClass" [disabled]="disabled" @@ -1221,7 +1222,7 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV updatePlaceHolderForFloatingLabel(): void { const parentElement = this.el.nativeElement.parentElement; - const isInFloatingLabel = parentElement.classList.contains('p-float-label'); + const isInFloatingLabel = parentElement?.classList.contains('p-float-label'); if (parentElement && isInFloatingLabel && !this.selectedOption) { const label = parentElement.querySelector('label'); if (label) { diff --git a/src/app/components/dynamicdialog/dynamicdialog.ts b/src/app/components/dynamicdialog/dynamicdialog.ts index 0e6f060f104..378ab0c535c 100755 --- a/src/app/components/dynamicdialog/dynamicdialog.ts +++ b/src/app/components/dynamicdialog/dynamicdialog.ts @@ -301,6 +301,7 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy { } this.renderer.setProperty(this.styleElement, 'innerHTML', innerHTML); + DomHandler.setAttribute(this.styleElement, 'nonce', this.primeNGConfig?.csp()?.nonce); } } } diff --git a/src/app/components/fileupload/fileupload.ts b/src/app/components/fileupload/fileupload.ts index 41a361de907..6eb20ef3e5b 100755 --- a/src/app/components/fileupload/fileupload.ts +++ b/src/app/components/fileupload/fileupload.ts @@ -140,7 +140,19 @@ import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEv
diff --git a/src/app/components/focustrap/focustrap.ts b/src/app/components/focustrap/focustrap.ts index 7ee8a6cac06..84bc3e02452 100755 --- a/src/app/components/focustrap/focustrap.ts +++ b/src/app/components/focustrap/focustrap.ts @@ -1,6 +1,6 @@ import { DomHandler } from 'primeng/dom'; import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { Directive, ElementRef, Input, NgModule, inject, booleanAttribute, PLATFORM_ID } from '@angular/core'; +import { Directive, ElementRef, Input, NgModule, inject, booleanAttribute, PLATFORM_ID, SimpleChanges } from '@angular/core'; /** * Focus Trap keeps focus within a certain DOM element while tabbing. @@ -31,10 +31,29 @@ export class FocusTrap { ngOnInit() { if (isPlatformBrowser(this.platformId) && !this.pFocusTrapDisabled) { - this.createHiddenFocusableElements(); + !this.firstHiddenFocusableElement && !this.lastHiddenFocusableElement && this.createHiddenFocusableElements(); } } + ngOnChanges(changes: SimpleChanges) { + if (changes.pFocusTrapDisabled && isPlatformBrowser(this.platformId)) { + if (changes.pFocusTrapDisabled.currentValue) { + this.removeHiddenFocusableElements(); + } else { + this.createHiddenFocusableElements(); + } + } + } + + removeHiddenFocusableElements() { + if (this.firstHiddenFocusableElement && this.firstHiddenFocusableElement.parentNode) { + this.firstHiddenFocusableElement.parentNode.removeChild(this.firstHiddenFocusableElement); + } + + if (this.lastHiddenFocusableElement && this.lastHiddenFocusableElement.parentNode) { + this.lastHiddenFocusableElement.parentNode.removeChild(this.lastHiddenFocusableElement); + } + } getComputedSelector(selector) { return `:not(.p-hidden-focusable):not([data-p-hidden-focusable="true"])${selector ?? ''}`; } diff --git a/src/app/components/galleria/galleria.css b/src/app/components/galleria/galleria.css index c996776c864..e58b38583f7 100755 --- a/src/app/components/galleria/galleria.css +++ b/src/app/components/galleria/galleria.css @@ -57,6 +57,11 @@ opacity: 1; } + .p-galleria-item-nav-onhover .p-galleria-item-nav-focused { + pointer-events: all; + opacity: 1; + } + .p-galleria-item-nav-onhover .p-galleria-item-wrapper:hover .p-galleria-item-nav.p-disabled { pointer-events: none; } diff --git a/src/app/components/galleria/galleria.ts b/src/app/components/galleria/galleria.ts index 09b7cffcb14..5d805c1ed09 100755 --- a/src/app/components/galleria/galleria.ts +++ b/src/app/components/galleria/galleria.ts @@ -10,6 +10,7 @@ import { DoCheck, ElementRef, EventEmitter, + HostListener, Inject, Input, KeyValueDiffers, @@ -67,6 +68,7 @@ import { FocusTrapModule } from 'primeng/focustrap'; (maskHide)="onMaskHide()" (activeItemChange)="onActiveItemChange($event)" [ngStyle]="containerStyle" + [fullScreen]="fullScreen" >
@@ -417,6 +419,7 @@ export class Galleria implements OnChanges, OnDestroy { [ngStyle]="!galleria.fullScreen ? galleria.containerStyle : {}" [class]="galleriaClass()" pFocusTrap + [pFocusTrapDisabled]="!fullScreen" >