mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-05 09:25:03 +00:00
Chore: update ng-select to v24, handle breaking changes (#13951)
This commit is contained in:
+6
-3
@@ -6,7 +6,7 @@
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge>
|
||||
}
|
||||
</button>
|
||||
<div class="px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||
<div [id]="dropdownMenuId" class="px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||
<ng-container *ngTemplateOutlet="list; context: { queries: selectionModel.queries }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,10 +64,11 @@
|
||||
[disabled]="disabled"
|
||||
[virtualScroll]="getSelectOptionsForField(atom.field)?.length > 100"
|
||||
[searchFn]="selectOptionSearchFn"
|
||||
[appendTo]="selectAppendTo"
|
||||
(mousedown)="$event.stopImmediatePropagation()"
|
||||
></ng-select>
|
||||
} @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.DocumentLink) {
|
||||
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true"></pngx-input-document-link>
|
||||
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true" [appendTo]="selectAppendTo"></pngx-input-document-link>
|
||||
} @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Monetary) {
|
||||
<input class="w-25 form-control rounded-end" type="text" inputmode="decimal"
|
||||
[ngModel]="atom.value"
|
||||
@@ -88,6 +89,7 @@
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
[searchFn]="customFieldSearchFn"
|
||||
[appendTo]="selectAppendTo"
|
||||
(mousedown)="$event.stopImmediatePropagation()"
|
||||
></ng-select>
|
||||
<select class="w-25 form-select" [(ngModel)]="atom.operator" [disabled]="disabled">
|
||||
@@ -121,7 +123,7 @@
|
||||
<ng-container *ngTemplateOutlet="comparisonValueTemplate; context: { atom: atom }"></ng-container>
|
||||
}
|
||||
@case (CustomFieldQueryOperator.Contains) {
|
||||
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true"></pngx-input-document-link>
|
||||
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true" [appendTo]="selectAppendTo"></pngx-input-document-link>
|
||||
}
|
||||
@case (CustomFieldQueryOperator.In) {
|
||||
<ng-select
|
||||
@@ -133,6 +135,7 @@
|
||||
[disabled]="disabled"
|
||||
[multiple]="true"
|
||||
[searchFn]="selectOptionSearchFn"
|
||||
[appendTo]="selectAppendTo"
|
||||
(mousedown)="$event.stopImmediatePropagation()"
|
||||
></ng-select>
|
||||
}
|
||||
|
||||
+13
@@ -1,3 +1,4 @@
|
||||
import { _IdGenerator } from '@angular/cdk/a11y'
|
||||
import {
|
||||
getLocaleNumberSymbol,
|
||||
NgClass,
|
||||
@@ -250,6 +251,18 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm
|
||||
@Input()
|
||||
useDropdown: boolean = true
|
||||
|
||||
private readonly idGenerator = inject(_IdGenerator)
|
||||
public readonly dropdownMenuId = this.idGenerator.getId(
|
||||
'pngx-custom-fields-query-dropdown-'
|
||||
)
|
||||
|
||||
/**
|
||||
* Keep ng-select dropdown panels inside the dropdown menu
|
||||
*/
|
||||
get selectAppendTo(): string {
|
||||
return this.useDropdown ? `#${this.dropdownMenuId}` : null
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs><div class="d-none d-sm-inline ms-1">{{title}}</div>
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div [id]="dropdownMenuId" class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
|
||||
@@ -21,6 +21,7 @@
|
||||
bindValue="id"
|
||||
bindLabel="name"
|
||||
clearable="false"
|
||||
[appendTo]="'#' + dropdownMenuId"
|
||||
placeholder="Relative dates"
|
||||
i18n-placeholder
|
||||
(change)="onSetCreatedRelativeDate($event)">
|
||||
@@ -97,6 +98,7 @@
|
||||
bindValue="id"
|
||||
bindLabel="name"
|
||||
clearable="false"
|
||||
[appendTo]="'#' + dropdownMenuId"
|
||||
placeholder="Relative dates"
|
||||
i18n-placeholder
|
||||
(change)="onSetAddedRelativeDate($event)">
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
||||
import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||
import { _IdGenerator } from '@angular/cdk/a11y'
|
||||
|
||||
export interface DateSelection {
|
||||
createdTo?: string
|
||||
@@ -68,6 +69,10 @@ export enum RelativeDate {
|
||||
})
|
||||
export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
public popperOptions = pngxPopperOptions
|
||||
private readonly idGenerator = inject(_IdGenerator)
|
||||
public readonly dropdownMenuId = this.idGenerator.getId(
|
||||
'pngx-dates-dropdown-'
|
||||
)
|
||||
|
||||
constructor() {
|
||||
const settings = inject(SettingsService)
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
[compareWith]="compareDocuments"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[appendTo]="appendTo"
|
||||
[loading]="loading()"
|
||||
[typeahead]="documentsInput$"
|
||||
(mousedown)="$event.stopImmediatePropagation()"
|
||||
|
||||
@@ -81,6 +81,12 @@ export class DocumentLinkComponent
|
||||
@Input()
|
||||
placeholder: string = $localize`Search for documents`
|
||||
|
||||
/**
|
||||
* Parent for ng-select dropdown, needed to prevent close on click.
|
||||
*/
|
||||
@Input()
|
||||
appendTo: string = null
|
||||
|
||||
get selectedDocumentIDs(): number[] {
|
||||
return this.selectedDocuments.map((d) => d.id)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
|
||||
// Dropdown hierarchy reveal for ng-select options
|
||||
:host ::ng-deep .ng-dropdown-panel .ng-option {
|
||||
::ng-deep .ng-dropdown-panel .ng-option {
|
||||
overflow-x: auto !important;
|
||||
|
||||
.tag-option-row {
|
||||
@@ -41,12 +41,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
||||
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
||||
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
||||
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
|
||||
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
||||
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
+6
-2
@@ -3,7 +3,7 @@
|
||||
<i-bs name="person-fill-lock"></i-bs><div class="d-none d-sm-inline ms-1">{{title}}</div>
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div [id]="dropdownMenuId" class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NONE)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@@ -58,7 +58,10 @@
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }" class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter() === OwnerFilterType.OTHERS) {
|
||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
||||
<a class="text-reset focus-variants" href="javascript:void(0)" (click)="clearIncludeUsers()" aria-label="Clear selected users" i18n-aria-label>
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1 w-100">
|
||||
@@ -69,6 +72,7 @@
|
||||
(ngModelChange)="selectionModel.includeUsers.set($event)"
|
||||
[disabled]="disabled"
|
||||
[clearable]="false"
|
||||
[appendTo]="'#' + dropdownMenuId"
|
||||
[items]="users()"
|
||||
bindLabel="username"
|
||||
multiple="true"
|
||||
|
||||
+15
@@ -6,3 +6,18 @@
|
||||
min-width: 1em;
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
.focus-variants {
|
||||
.variant-focused {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
.variant-unfocused {
|
||||
display: none;
|
||||
}
|
||||
.variant-focused {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -171,6 +171,21 @@ describe('PermissionsFilterDropdownComponent', () => {
|
||||
|
||||
expect(component.selectionModel.ownerFilter()).toEqual(OwnerFilterType.NONE)
|
||||
})
|
||||
it('should clear selected users', () => {
|
||||
component.selectionModel.includeUsers.set([12])
|
||||
component.onUserSelect()
|
||||
component.selectionModel.hideUnowned.set(true)
|
||||
expect(component.selectionModel.ownerFilter()).toEqual(
|
||||
OwnerFilterType.OTHERS
|
||||
)
|
||||
|
||||
component.clearIncludeUsers()
|
||||
|
||||
expect(component.selectionModel.includeUsers()).toEqual([])
|
||||
expect(component.selectionModel.ownerFilter()).toEqual(OwnerFilterType.NONE)
|
||||
expect(component.selectionModel.hideUnowned()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should emit a selection model depending on the type of owner filter set', () => {
|
||||
const emitted = () => ({
|
||||
excludeUsers: ownerFilterSetResult.excludeUsers(),
|
||||
|
||||
+11
@@ -1,3 +1,4 @@
|
||||
import { _IdGenerator } from '@angular/cdk/a11y'
|
||||
import { NgClass } from '@angular/common'
|
||||
import {
|
||||
Component,
|
||||
@@ -70,6 +71,11 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
|
||||
|
||||
public OwnerFilterType = OwnerFilterType
|
||||
|
||||
private readonly idGenerator = inject(_IdGenerator)
|
||||
public readonly dropdownMenuId = this.idGenerator.getId(
|
||||
'pngx-permissions-filter-dropdown-'
|
||||
)
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@@ -205,6 +211,11 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
|
||||
this.ownerFilterSet.emit(this.selectionModel)
|
||||
}
|
||||
|
||||
clearIncludeUsers() {
|
||||
this.selectionModel.includeUsers.set([])
|
||||
this.onUserSelect()
|
||||
}
|
||||
|
||||
onUserSelect() {
|
||||
this.selectionModel.ownerFilter.set(
|
||||
this.selectionModel.includeUsers()?.length
|
||||
|
||||
+16
-11
@@ -374,17 +374,6 @@ th[pngxSortable] {
|
||||
}
|
||||
}
|
||||
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
|
||||
background-color: var(--pngx-bg-darker) !important;
|
||||
color: var(--pngx-body-color-accent) !important;
|
||||
}
|
||||
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected,
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked {
|
||||
font-weight: bold;
|
||||
background-color: var(--pngx-bg-alt2) !important;
|
||||
}
|
||||
|
||||
&.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder,
|
||||
.ng-select-container .ng-value-container .ng-placeholder {
|
||||
top: 7px;
|
||||
@@ -402,6 +391,22 @@ th[pngxSortable] {
|
||||
}
|
||||
}
|
||||
|
||||
// ng-select renders the dropdown panel in a CDK overlay outside the component
|
||||
.ng-dropdown-panel {
|
||||
font-size: var(--pngx-body-font-size);
|
||||
|
||||
.ng-dropdown-panel-items .ng-option.ng-option-marked {
|
||||
background-color: var(--pngx-bg-darker) !important;
|
||||
color: var(--pngx-body-color-accent) !important;
|
||||
}
|
||||
|
||||
.ng-dropdown-panel-items .ng-option.ng-option-selected,
|
||||
.ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked {
|
||||
font-weight: bold;
|
||||
background-color: var(--pngx-bg-alt2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.paperless-input-tags {
|
||||
.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value {
|
||||
background-color: transparent;
|
||||
|
||||
@@ -237,8 +237,8 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,%3csvg xmlns='h
|
||||
mix-blend-mode: luminosity;
|
||||
}
|
||||
|
||||
.paperless-input-select .ng-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:not(.ng-option-selected):hover,
|
||||
.paperless-input-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option:not(.ng-option-selected):hover,
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
|
||||
background-color: var(--bs-light) !important;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user