From da9a8dfa41c31d694b56703c1c9efc9a62041648 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Wed, 5 Nov 2025 16:46:28 +0100 Subject: [PATCH 01/23] OO-50008 focus order for Search field fixed --- .../bits/src/lib/search/search.component.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/bits/src/lib/search/search.component.html b/packages/bits/src/lib/search/search.component.html index 867350f40..8793a3c04 100644 --- a/packages/bits/src/lib/search/search.component.html +++ b/packages/bits/src/lib/search/search.component.html @@ -1,5 +1,18 @@
+
-
From fac3ecd5cb7e94eff4bea82c2214b9545d61a441 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Tue, 11 Nov 2025 22:22:38 +0100 Subject: [PATCH 02/23] OO-52367 Spacebar opens Search popup --- packages/bits/src/lib/popup/popup-toggle.directive.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/bits/src/lib/popup/popup-toggle.directive.ts b/packages/bits/src/lib/popup/popup-toggle.directive.ts index 02f08862d..818c25978 100644 --- a/packages/bits/src/lib/popup/popup-toggle.directive.ts +++ b/packages/bits/src/lib/popup/popup-toggle.directive.ts @@ -48,4 +48,15 @@ export class PopupToggleDirective { this.toggle.emit(event); } } + + @HostListener("keydown", ["$event"]) + public handleKeydown(event: KeyboardEvent): void { + if (this.isDisabled || this.disabled) { + return; + } + if (event.key === " " || event.key === "Enter") { + event.preventDefault(); + this.toggle.emit(event); + } + } } From 384ab71e7a56c7343ba2ca88266280648f9fbe9d Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 13 Nov 2025 16:38:48 +0100 Subject: [PATCH 03/23] OO-52367 Esc and Space works --- packages/bits/src/lib/search/search.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bits/src/lib/search/search.component.ts b/packages/bits/src/lib/search/search.component.ts index 629f99164..6071cfd13 100644 --- a/packages/bits/src/lib/search/search.component.ts +++ b/packages/bits/src/lib/search/search.component.ts @@ -141,6 +141,8 @@ export class SearchComponent implements IFilterPub, OnDestroy { public onKeyup(event: KeyboardEvent): void { if (event.key === "Enter") { this.onSearch(); + } else if (event.key === "Escape" || event.key === "Esc") { + this.onCancel(); } } From 929951ecd91db97d5bd1ab0455b7736281b52a06 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Mon, 17 Nov 2025 14:59:29 +0100 Subject: [PATCH 04/23] OO-52365 labels and controls --- .../bits/src/lib/search/search.component.html | 26 +++-- .../bits/src/lib/search/search.component.ts | 98 ++++++++++++------- 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/packages/bits/src/lib/search/search.component.html b/packages/bits/src/lib/search/search.component.html index 8793a3c04..5d38da4a1 100644 --- a/packages/bits/src/lib/search/search.component.html +++ b/packages/bits/src/lib/search/search.component.html @@ -1,23 +1,33 @@
+
diff --git a/packages/bits/src/lib/search/search.component.ts b/packages/bits/src/lib/search/search.component.ts index 88c079042..647005033 100644 --- a/packages/bits/src/lib/search/search.component.ts +++ b/packages/bits/src/lib/search/search.component.ts @@ -36,7 +36,7 @@ import { IFilter, IFilterPub } from "../../services/data-source/public-api"; selector: "nui-search", host: { class: "nui-search", - role: "search", + role: "searchbox", }, templateUrl: "./search.component.html", styleUrls: ["./search.component.less"], From e8b290ccb78d02155f589cfee56d9550e69c31a7 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Tue, 18 Nov 2025 00:39:59 +0100 Subject: [PATCH 06/23] NUI-6263 Recent searches accessible by Tab key --- .../repeat/repeat-item/repeat-item.component.less | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/bits/src/lib/repeat/repeat-item/repeat-item.component.less b/packages/bits/src/lib/repeat/repeat-item/repeat-item.component.less index d6a199311..0c7724331 100644 --- a/packages/bits/src/lib/repeat/repeat-item/repeat-item.component.less +++ b/packages/bits/src/lib/repeat/repeat-item/repeat-item.component.less @@ -14,12 +14,14 @@ body > nui-repeat-item.cdk-drag-preview .nui-repeat-item { .nui .nui-repeat-item { &:active, - &:focus { + &:focus, + &:focus-within { outline: none; + .setCssVariable(background-color, nui-color-bg-transparent-hover); } &:hover { - .setCssVariable(background-color, nui-color-bg-secondary); + .setCssVariable(background-color, nui-color-bg-transparent-hover); } &__content { @@ -77,8 +79,9 @@ body > nui-repeat-item.cdk-drag-preview .nui-repeat-item { &.nui-repeat-item--clickable { &:not(.nui-repeat-item--selected) { - &:hover { - .setCssVariable(background-color, nui-color-bg-secondary); + &:hover, + &:focus { + .setCssVariable(background-color, nui-color-bg-transparent-hover); } } } @@ -88,7 +91,8 @@ body > nui-repeat-item.cdk-drag-preview .nui-repeat-item { .setCssVariable(color, nui-color-text-default); &.nui-repeat-item--clickable { - &:hover { + &:hover, + &:focus { .setCssVariable(background-color, nui-color-selected-hover); } } From 2431d79f0a386c9f13b15d9d722745b13a592a6b Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 27 Nov 2025 12:39:55 +0100 Subject: [PATCH 07/23] OO-55517 icon component role --- packages/bits/src/lib/icon/icon.component.ts | 71 +++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/bits/src/lib/icon/icon.component.ts b/packages/bits/src/lib/icon/icon.component.ts index 3bb4c4106..f0fddc2c1 100644 --- a/packages/bits/src/lib/icon/icon.component.ts +++ b/packages/bits/src/lib/icon/icon.component.ts @@ -43,8 +43,9 @@ import { IconData, IconStatus } from "./types"; changeDetection: ChangeDetectionStrategy.OnPush, host: { class: "nui-icon-wrapper", - role: "img", - "[attr.aria-label]": "icon + ' icon'", + "[attr.role]": "computedRole", + "[attr.aria-hidden]": "computedAriaHidden", + "[attr.aria-label]": "computedAriaLabel", }, styleUrls: ["./icon.component.less"], encapsulation: ViewEncapsulation.None, @@ -75,6 +76,19 @@ export class IconComponent implements OnChanges { childStatus: IconStatus; @Input() icon: string; + /** + * Marks the icon as purely decorative. Decorative icons are hidden from assistive technologies. + */ + @Input() decorative?: boolean; + /** + * Accessible name for a meaningful icon. Ignored if `decorative` is true or empty. + */ + @Input() ariaLabel?: string; + /** + * Optional explicit role override. Constrained to 'img' | 'presentation' | null. + * If omitted, role is inferred from `decorative` and `ariaLabel`. + */ + @Input() explicitRole?: "img" | "presentation" | null; public resultingSvg: SafeHtml; @@ -155,6 +169,59 @@ export class IconComponent implements OnChanges { } } + // --- Accessibility computed properties --- + private get isDecorative(): boolean { + // Treat undefined as true for backward-compatible default decorative behavior + return this.decorative !== false; + } + + get computedRole(): string | null { + if (this.isDecorative) { + return "presentation"; + } + const label = this.normalizedAriaLabel; + if (this.explicitRole) { + if (this.explicitRole === "img") { + return label ? "img" : null; // avoid img without name + } + if (this.explicitRole === "presentation") { + return "presentation"; + } + } + return label ? "img" : null; + } + + get computedAriaHidden(): string | null { + // Hide if decorative or no semantic role (no label) to keep DOM clean for AT + return this.isDecorative || this.computedRole !== "img" ? "true" : null; + } + + get computedAriaLabel(): string | null { + if (this.computedRole !== "img") { + return null; + } + const base = this.normalizedAriaLabel; + if (!base) { + return null; + } + const statusParts: string[] = []; + if (this.status) { + statusParts.push(this.status.toLowerCase()); + } + if (this.childStatus) { + statusParts.push(this.childStatus.toLowerCase()); + } + return statusParts.length ? `${base} ${statusParts.join(" ")}` : base; + } + + private get normalizedAriaLabel(): string | null { + if (this.isDecorative) { + return null; + } + const trimmed = (this.ariaLabel || "").trim(); + return trimmed.length ? trimmed : null; + } + private generateIcon() { this.iconData = this.iconService.getIconData(this.icon); this.iconFound = !!this.iconData; From 3b4287588d933781ded5cacc4a258907e23ef227 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 27 Nov 2025 12:49:27 +0100 Subject: [PATCH 08/23] OO-55517 image component role --- .../bits/src/lib/image/image.component.ts | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/bits/src/lib/image/image.component.ts b/packages/bits/src/lib/image/image.component.ts index e9b165827..14c9b5cdf 100644 --- a/packages/bits/src/lib/image/image.component.ts +++ b/packages/bits/src/lib/image/image.component.ts @@ -55,8 +55,9 @@ import { UtilService } from "../../services/util.service"; styleUrls: ["./image.component.less"], encapsulation: ViewEncapsulation.None, host: { - role: "img", - "[attr.aria-label]": "hasAlt ? null : description || imageName", + "[attr.role]": "computedRole", + "[attr.aria-hidden]": "computedAriaHidden", + "[attr.aria-label]": "computedAriaLabel", }, standalone: false, }) @@ -100,6 +101,12 @@ export class ImageComponent implements OnInit, AfterViewInit, OnChanges { public imageName: string | null; public hasAlt: boolean; + /** + * Optional ARIA role override. Constrained to 'img' | 'presentation' | null. + * If omitted, role is inferred from presence of alt/label. + */ + @Input() public role?: "img" | "presentation" | null; + constructor( private logger: LoggerService, private utilService: UtilService, @@ -129,13 +136,49 @@ export class ImageComponent implements OnInit, AfterViewInit, OnChanges { } } + // --- Accessibility computed properties --- + public get computedRole(): string | null { + // If explicit role provided, honor it with safeguards + if (this.role) { + if (this.role === "img") { + return this.normalizedLabel ? "img" : null; // avoid img without name + } + if (this.role === "presentation") { + return "presentation"; + } + } + // Infer role: if we have alt (hasAlt) or a label, expose as img; otherwise presentation + if (this.hasAlt || this.normalizedLabel) { + return "img"; + } + return "presentation"; + } + + public get computedAriaHidden(): string | null { + // Hide when not exposed as img + return this.computedRole !== "img" ? "true" : null; + } + + public get computedAriaLabel(): string | null { + // Only provide aria-label when role is img and there's no native alt + if (this.computedRole !== "img" || this.hasAlt) { + return null; + } + return this.normalizedLabel; + } + + private get normalizedLabel(): string | null { + const candidate = (this.description || this.imageName || "").trim(); + return candidate.length ? candidate : null; + } + public ngAfterViewInit(): void { if (this.autoFill) { try { const svg = this.el.nativeElement.querySelector("svg"); svg.setAttribute("width", "100%"); svg.setAttribute("height", "100%"); - } catch (e) { + } catch { console.warn( "Can't apply 'autoFill' to nui-image, because it is only applicable to SVG type of images" ); From 46423db4e33be1a714e6fb2b4836eb5f2a036a9a Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 27 Nov 2025 13:08:47 +0100 Subject: [PATCH 09/23] OO-55517 computeA11yForGraphic --- .../src/functions/a11y-graphics.util.spec.ts | 96 +++++++++++++++++++ .../bits/src/functions/a11y-graphics.util.ts | 73 ++++++++++++++ packages/bits/src/lib/icon/icon.component.ts | 60 ++++-------- .../bits/src/lib/image/image.component.ts | 42 +++----- 4 files changed, 201 insertions(+), 70 deletions(-) create mode 100644 packages/bits/src/functions/a11y-graphics.util.spec.ts create mode 100644 packages/bits/src/functions/a11y-graphics.util.ts diff --git a/packages/bits/src/functions/a11y-graphics.util.spec.ts b/packages/bits/src/functions/a11y-graphics.util.spec.ts new file mode 100644 index 000000000..a84b9d1f6 --- /dev/null +++ b/packages/bits/src/functions/a11y-graphics.util.spec.ts @@ -0,0 +1,96 @@ +import { + computeA11yForGraphic, + A11yGraphicOptions, +} from "./a11y-graphics.util"; + +/** Helper to reduce repetition */ +function run(opts: A11yGraphicOptions) { + return computeA11yForGraphic(opts); +} + +describe("computeA11yForGraphic", () => { + it("defaults to decorative when decorative undefined", () => { + const r = run({ label: "Server" }); + expect(r.role).toBe("presentation"); + expect(r.ariaHidden).toBe("true"); + expect(r.ariaLabel).toBeNull(); + }); + + it("returns img role and aria-label when decorative=false and label present", () => { + const r = run({ + decorative: false, + label: "Warning", + statusParts: ["critical"], + }); + expect(r.role).toBe("img"); + expect(r.ariaHidden).toBeNull(); + expect(r.ariaLabel).toBe("Warning critical"); + }); + + it("omits aria-label when hasAlt=true even if label present", () => { + const r = run({ decorative: false, label: "Logo", hasAlt: true }); + expect(r.role).toBe("img"); + expect(r.ariaHidden).toBeNull(); + expect(r.ariaLabel).toBeNull(); + }); + + it("explicitRole=img without label yields null role (no empty img)", () => { + const r = run({ decorative: false, explicitRole: "img", label: "" }); + expect(r.role).toBeNull(); + expect(r.ariaHidden).toBe("true"); + expect(r.ariaLabel).toBeNull(); + }); + + it("explicitRole=img with label respected", () => { + const r = run({ + decorative: false, + explicitRole: "img", + label: "Battery", + }); + expect(r.role).toBe("img"); + expect(r.ariaHidden).toBeNull(); + expect(r.ariaLabel).toBe("Battery"); + }); + + it("explicitRole=presentation overrides label", () => { + const r = run({ + decorative: false, + explicitRole: "presentation", + label: "Chart", + }); + expect(r.role).toBe("presentation"); + expect(r.ariaHidden).toBe("true"); + expect(r.ariaLabel).toBeNull(); + }); + + it("infers img when hasAlt true even without label", () => { + const r = run({ decorative: false, hasAlt: true }); + expect(r.role).toBe("img"); + expect(r.ariaHidden).toBeNull(); + expect(r.ariaLabel).toBeNull(); + }); + + it("infers presentation when no alt and no label", () => { + const r = run({ decorative: false }); + expect(r.role).toBe("presentation"); + expect(r.ariaHidden).toBe("true"); + expect(r.ariaLabel).toBeNull(); + }); + + it("statusParts ignored if empty strings", () => { + const r = run({ + decorative: false, + label: "Node", + statusParts: ["", " ", null as any], + }); + expect(r.role).toBe("img"); + expect(r.ariaLabel).toBe("Node"); + }); + + it("decorative=true forces presentation regardless of label", () => { + const r = run({ decorative: true, label: "CPU" }); + expect(r.role).toBe("presentation"); + expect(r.ariaHidden).toBe("true"); + expect(r.ariaLabel).toBeNull(); + }); +}); diff --git a/packages/bits/src/functions/a11y-graphics.util.ts b/packages/bits/src/functions/a11y-graphics.util.ts new file mode 100644 index 000000000..2a8eb14f5 --- /dev/null +++ b/packages/bits/src/functions/a11y-graphics.util.ts @@ -0,0 +1,73 @@ +export interface A11yGraphicOptions { + decorative?: boolean; // undefined treated as true by caller if desired + explicitRole?: "img" | "presentation" | null; + label?: string | null; + hasAlt?: boolean; // true if native exists + statusParts?: string[]; // additional semantic qualifiers (statuses) +} + +export interface A11yGraphicResult { + role: string | null; + ariaHidden: string | null; + ariaLabel: string | null; +} + +/** + * Computes role, aria-hidden, and aria-label for graphics (icons/images) with consistent rules: + * - Decorative defaults to presentation + aria-hidden + * - role="img" only if a non-empty accessible name is available + * - Explicit role overrides are honored but guarded against invalid accessible name cases + */ +export function computeA11yForGraphic( + opts: A11yGraphicOptions +): A11yGraphicResult { + const decorative = opts.decorative !== false; // undefined => decorative + const normalizedLabel = normalizeLabel( + decorative ? null : opts.label ?? null + ); + const statusLabel = buildStatusLabel(normalizedLabel, opts.statusParts); + + let role: string | null = null; + if (decorative) { + role = "presentation"; + } else if (opts.explicitRole) { + if (opts.explicitRole === "img") { + role = normalizedLabel ? "img" : null; + } else if (opts.explicitRole === "presentation") { + role = "presentation"; + } + } else { + // Infer role from presence of alt or label + if (opts.hasAlt || normalizedLabel) { + role = "img"; + } else { + role = "presentation"; + } + } + + const ariaHidden = role !== "img" ? "true" : null; + + // Provide aria-label only if role is img, no native alt, and we have a label + const ariaLabel = role === "img" && !opts.hasAlt ? statusLabel : null; + + return { role, ariaHidden, ariaLabel }; +} + +function normalizeLabel(raw: string | null | undefined): string | null { + if (!raw) { + return null; + } + const trimmed = raw.trim(); + return trimmed.length ? trimmed : null; +} + +function buildStatusLabel( + base: string | null, + parts?: string[] +): string | null { + if (!base) { + return null; + } + const valid = (parts || []).map((p) => p?.trim()).filter((p) => !!p); + return valid.length ? `${base} ${valid.join(" ")}` : base; +} diff --git a/packages/bits/src/lib/icon/icon.component.ts b/packages/bits/src/lib/icon/icon.component.ts index f0fddc2c1..14a4f7b09 100644 --- a/packages/bits/src/lib/icon/icon.component.ts +++ b/packages/bits/src/lib/icon/icon.component.ts @@ -32,6 +32,7 @@ import isNil from "lodash/isNil"; import { IconService } from "./icon.service"; import { IconData, IconStatus } from "./types"; +import { computeA11yForGraphic } from "../../functions/a11y-graphics.util"; /** * ./../examples/index.html#/icon @@ -169,41 +170,8 @@ export class IconComponent implements OnChanges { } } - // --- Accessibility computed properties --- - private get isDecorative(): boolean { - // Treat undefined as true for backward-compatible default decorative behavior - return this.decorative !== false; - } - - get computedRole(): string | null { - if (this.isDecorative) { - return "presentation"; - } - const label = this.normalizedAriaLabel; - if (this.explicitRole) { - if (this.explicitRole === "img") { - return label ? "img" : null; // avoid img without name - } - if (this.explicitRole === "presentation") { - return "presentation"; - } - } - return label ? "img" : null; - } - - get computedAriaHidden(): string | null { - // Hide if decorative or no semantic role (no label) to keep DOM clean for AT - return this.isDecorative || this.computedRole !== "img" ? "true" : null; - } - - get computedAriaLabel(): string | null { - if (this.computedRole !== "img") { - return null; - } - const base = this.normalizedAriaLabel; - if (!base) { - return null; - } + // --- Accessibility via shared util --- + private get a11y() { const statusParts: string[] = []; if (this.status) { statusParts.push(this.status.toLowerCase()); @@ -211,15 +179,23 @@ export class IconComponent implements OnChanges { if (this.childStatus) { statusParts.push(this.childStatus.toLowerCase()); } - return statusParts.length ? `${base} ${statusParts.join(" ")}` : base; + return computeA11yForGraphic({ + decorative: this.decorative, + explicitRole: this.explicitRole, + label: this.ariaLabel || this.icon || null, + hasAlt: false, + statusParts, + }); } - private get normalizedAriaLabel(): string | null { - if (this.isDecorative) { - return null; - } - const trimmed = (this.ariaLabel || "").trim(); - return trimmed.length ? trimmed : null; + get computedRole(): string | null { + return this.a11y.role; + } + get computedAriaHidden(): string | null { + return this.a11y.ariaHidden; + } + get computedAriaLabel(): string | null { + return this.a11y.ariaLabel; } private generateIcon() { diff --git a/packages/bits/src/lib/image/image.component.ts b/packages/bits/src/lib/image/image.component.ts index 14c9b5cdf..b9eabb83b 100644 --- a/packages/bits/src/lib/image/image.component.ts +++ b/packages/bits/src/lib/image/image.component.ts @@ -44,6 +44,7 @@ import { IImagesPresetItem } from "./public-api"; import { imagesPresetToken } from "../../constants/images.constants"; import { LoggerService } from "../../services/log-service"; import { UtilService } from "../../services/util.service"; +import { computeA11yForGraphic } from "../../functions/a11y-graphics.util"; /** * ./../examples/index.html#/image @@ -137,39 +138,24 @@ export class ImageComponent implements OnInit, AfterViewInit, OnChanges { } // --- Accessibility computed properties --- - public get computedRole(): string | null { - // If explicit role provided, honor it with safeguards - if (this.role) { - if (this.role === "img") { - return this.normalizedLabel ? "img" : null; // avoid img without name - } - if (this.role === "presentation") { - return "presentation"; - } - } - // Infer role: if we have alt (hasAlt) or a label, expose as img; otherwise presentation - if (this.hasAlt || this.normalizedLabel) { - return "img"; - } - return "presentation"; + private get a11y() { + return computeA11yForGraphic({ + decorative: false, // images default to semantic unless lacking label/alt + explicitRole: this.role, + label: this.description || this.imageName || null, + hasAlt: this.hasAlt, + statusParts: undefined, + }); } + public get computedRole(): string | null { + return this.a11y.role; + } public get computedAriaHidden(): string | null { - // Hide when not exposed as img - return this.computedRole !== "img" ? "true" : null; + return this.a11y.ariaHidden; } - public get computedAriaLabel(): string | null { - // Only provide aria-label when role is img and there's no native alt - if (this.computedRole !== "img" || this.hasAlt) { - return null; - } - return this.normalizedLabel; - } - - private get normalizedLabel(): string | null { - const candidate = (this.description || this.imageName || "").trim(); - return candidate.length ? candidate : null; + return this.a11y.ariaLabel; } public ngAfterViewInit(): void { From 4e3d011f4ffc0e14b67c68f4ea0682a6fa1d9cf2 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Wed, 14 Jan 2026 13:36:18 +0100 Subject: [PATCH 10/23] NUI-6263 compute-version-local script --- package.json | 2 +- packages/bits/package.json | 2 +- packages/bits/schematics/package.json | 2 +- packages/charts/package.json | 4 ++-- packages/dashboards/package.json | 8 ++++---- packages/dashboards/schematics/package.json | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ff69f3909..ac9f43b73 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "trigger-pipeline-build-ci": "bash scripts/trigger-pipeline-build", "verify-ci": "bash scripts/verify-published" }, - "version": "19.0.0", + "version": "19.0.2-0", "workspaces": [ "packages/*" ] diff --git a/packages/bits/package.json b/packages/bits/package.json index 8ce910555..de6811431 100644 --- a/packages/bits/package.json +++ b/packages/bits/package.json @@ -136,6 +136,6 @@ "visual:watch": "npx watch \"yarn run visual:base\" src demo spec --watch=1" }, "typings": "public_api.d.ts", - "version": "19.0.0", + "version": "19.0.2-0", "packageManager": "yarn@1.22.18" } diff --git a/packages/bits/schematics/package.json b/packages/bits/schematics/package.json index 2e601255a..79f8f850d 100644 --- a/packages/bits/schematics/package.json +++ b/packages/bits/schematics/package.json @@ -1,7 +1,7 @@ { "name": "nova-schematics", "license": "Apache-2.0", - "version": "19.0.0", + "version": "19.0.2-0", "scripts": { "assemble": "run-s build copy:json copy:data test copy:dist", "build": "tsc -p tsconfig.json", diff --git a/packages/charts/package.json b/packages/charts/package.json index 9c2b9bf5c..e612b9e52 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -37,7 +37,7 @@ "license": "Apache-2.0", "name": "@nova-ui/charts", "dependencies": { - "@nova-ui/bits": "~19.0.0" + "@nova-ui/bits": "~19.0.2-0" }, "peerDependencies": { "@types/d3": "^5.0.0", @@ -102,5 +102,5 @@ "visual:gui": "yarn run visual:base -c gui", "visual:serve": "yarn run visual:base -c serve" }, - "version": "19.0.0" + "version": "19.0.2-0" } \ No newline at end of file diff --git a/packages/dashboards/package.json b/packages/dashboards/package.json index d872bb7a4..29e2a9891 100644 --- a/packages/dashboards/package.json +++ b/packages/dashboards/package.json @@ -33,8 +33,8 @@ "license": "Apache-2.0", "name": "@nova-ui/dashboards", "dependencies": { - "@nova-ui/bits": "~19.0.0", - "@nova-ui/charts": "~19.0.0" + "@nova-ui/bits": "~19.0.2-0", + "@nova-ui/charts": "~19.0.2-0" }, "devDependencies": { "@apollo/client": "3.7.3", @@ -105,5 +105,5 @@ "visual:gui": "yarn run visual:base -c gui", "visual:serve": "yarn run visual:base -c serve" }, - "version": "19.0.0" -} + "version": "19.0.2-0" +} \ No newline at end of file diff --git a/packages/dashboards/schematics/package.json b/packages/dashboards/schematics/package.json index 22dbe9d8a..af6066a1a 100644 --- a/packages/dashboards/schematics/package.json +++ b/packages/dashboards/schematics/package.json @@ -1,7 +1,7 @@ { "name": "dashboards-schematics", "license": "Apache-2.0", - "version": "19.0.0", + "version": "19.0.2-0", "scripts": { "assemble": "run-s build copy:json copy:data test copy:dist", "build": "tsc -p tsconfig.json", From 995b30796e5d80afc553de6270326fd4b187aaba Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Wed, 14 Jan 2026 16:44:13 +0100 Subject: [PATCH 11/23] NUI-6263 yarn run --cwd packages/bits lint:fix --- .../color-picker-basic.example.component.ts | 4 ++-- .../color-picker-palette.example.component.ts | 5 +++-- .../color-picker-select.example.component.ts | 5 +++-- .../components/demo/color-picker/color-picker.module.ts | 7 ++++--- .../src/lib/color-picker/color-picker.component.spec.ts | 4 ++-- .../bits/src/lib/color-picker/color-picker.component.ts | 7 ++++--- packages/bits/src/lib/color-picker/color-picker.module.ts | 4 ++-- packages/bits/src/lib/color-picker/color.service.ts | 1 + packages/bits/src/lib/image/image.component.ts | 2 +- packages/bits/src/lib/search/search.component.ts | 3 +-- 10 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/bits/demo/src/components/demo/color-picker/color-picker-basic/color-picker-basic.example.component.ts b/packages/bits/demo/src/components/demo/color-picker/color-picker-basic/color-picker-basic.example.component.ts index 1495b144a..34fbfb295 100644 --- a/packages/bits/demo/src/components/demo/color-picker/color-picker-basic/color-picker-basic.example.component.ts +++ b/packages/bits/demo/src/components/demo/color-picker/color-picker-basic/color-picker-basic.example.component.ts @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormControl, @@ -45,7 +45,7 @@ const CHART_PALETTE_CS1: string[] = [ styles: [], standalone: false, }) -export class ColorPickerBasicExampleComponent { +export class ColorPickerBasicExampleComponent implements OnInit { public myForm: FormGroup<{ backgroundColor: FormControl }>; public colors: string[] = CHART_PALETTE_CS1; diff --git a/packages/bits/demo/src/components/demo/color-picker/color-picker-palette/color-picker-palette.example.component.ts b/packages/bits/demo/src/components/demo/color-picker/color-picker-palette/color-picker-palette.example.component.ts index ff4245887..42e72d41e 100644 --- a/packages/bits/demo/src/components/demo/color-picker/color-picker-palette/color-picker-palette.example.component.ts +++ b/packages/bits/demo/src/components/demo/color-picker/color-picker-palette/color-picker-palette.example.component.ts @@ -18,12 +18,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormControl, FormGroup, } from "@angular/forms"; + import { HTML_COLORS, IPaletteColor } from "../../../../../../src/constants/color-picker.constants"; @@ -33,7 +34,7 @@ import { HTML_COLORS, IPaletteColor } from "../../../../../../src/constants/colo styles: [], standalone: false, }) -export class ColorPickerPaletteExampleComponent { +export class ColorPickerPaletteExampleComponent implements OnInit { public myForm: FormGroup<{ backgroundColor: FormControl }>; public colorPalette: IPaletteColor[] = Array.from(HTML_COLORS.entries()) .map(([label, color]) => ({label,color})); diff --git a/packages/bits/demo/src/components/demo/color-picker/color-picker-select/color-picker-select.example.component.ts b/packages/bits/demo/src/components/demo/color-picker/color-picker-select/color-picker-select.example.component.ts index 7ed2ee0f0..1c19aa4a2 100644 --- a/packages/bits/demo/src/components/demo/color-picker/color-picker-select/color-picker-select.example.component.ts +++ b/packages/bits/demo/src/components/demo/color-picker/color-picker-select/color-picker-select.example.component.ts @@ -18,12 +18,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormControl, FormGroup, } from "@angular/forms"; + import { HTML_COLORS, IPaletteColor } from "../../../../../../src/constants/color-picker.constants"; @@ -33,7 +34,7 @@ import { HTML_COLORS, IPaletteColor } from "../../../../../../src/constants/colo styles: [], standalone: false, }) -export class ColorPickerSelectExampleComponent { +export class ColorPickerSelectExampleComponent implements OnInit { public myForm: FormGroup<{ backgroundColor: FormControl }>; public colorPalette: IPaletteColor[] = Array.from(HTML_COLORS.entries()) .map(([label, color]) => ({label,color})); diff --git a/packages/bits/demo/src/components/demo/color-picker/color-picker.module.ts b/packages/bits/demo/src/components/demo/color-picker/color-picker.module.ts index 2192110f3..60b898bf4 100644 --- a/packages/bits/demo/src/components/demo/color-picker/color-picker.module.ts +++ b/packages/bits/demo/src/components/demo/color-picker/color-picker.module.ts @@ -19,8 +19,8 @@ // THE SOFTWARE. import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; import { DEMO_PATH_TOKEN, @@ -30,11 +30,12 @@ import { NuiPopoverModule, SrlcStage, } from "@nova-ui/bits"; -import { getDemoFiles } from "../../../static/demo-files-factory"; + import { ColorPickerBasicExampleComponent } from "./color-picker-basic/color-picker-basic.example.component"; +import { ColorPickerExampleComponent } from "./color-picker-docs/color-picker-docs.example.component"; import { ColorPickerPaletteExampleComponent } from "./color-picker-palette/color-picker-palette.example.component"; import { ColorPickerSelectExampleComponent } from "./color-picker-select/color-picker-select.example.component"; -import { ColorPickerExampleComponent } from "./color-picker-docs/color-picker-docs.example.component"; +import { getDemoFiles } from "../../../static/demo-files-factory"; const routes = [ { diff --git a/packages/bits/src/lib/color-picker/color-picker.component.spec.ts b/packages/bits/src/lib/color-picker/color-picker.component.spec.ts index 919c62bee..cd0c72182 100644 --- a/packages/bits/src/lib/color-picker/color-picker.component.spec.ts +++ b/packages/bits/src/lib/color-picker/color-picker.component.spec.ts @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +import { Overlay } from "@angular/cdk/overlay"; import { Component, CUSTOM_ELEMENTS_SCHEMA, @@ -26,11 +27,10 @@ import { } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; -import { Overlay } from "@angular/cdk/overlay"; +import { Subject } from "rxjs/internal/Subject"; import { ColorPickerComponent } from "./color-picker.component"; import { ColorService } from "./color.service"; -import { Subject } from "rxjs/internal/Subject"; @Component({ diff --git a/packages/bits/src/lib/color-picker/color-picker.component.ts b/packages/bits/src/lib/color-picker/color-picker.component.ts index baf7d0cf0..f24a75bdf 100644 --- a/packages/bits/src/lib/color-picker/color-picker.component.ts +++ b/packages/bits/src/lib/color-picker/color-picker.component.ts @@ -34,11 +34,12 @@ import { import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { Subject } from "rxjs"; import { takeUntil, tap } from "rxjs/operators"; -import { ColorService } from "./color.service"; -import { getColorValueByName } from "./../../functions/color.helper"; -import { IPaletteColor } from "./../../constants/color-picker.constants"; + import { getOverlayPositions, IOptionValueObject, IResizeConfig, NuiFormFieldControl, OverlayUtilitiesService } from "../public-api"; import { SelectV2Component } from "../select-v2/select/select-v2.component"; +import { IPaletteColor } from "./../../constants/color-picker.constants"; +import { getColorValueByName } from "./../../functions/color.helper"; +import { ColorService } from "./color.service"; // Left and right paddings of .color-picker-container element const CONTAINER_SIDE_PADDINGS_PX: number = 20; diff --git a/packages/bits/src/lib/color-picker/color-picker.module.ts b/packages/bits/src/lib/color-picker/color-picker.module.ts index 618632001..2d23d42b5 100644 --- a/packages/bits/src/lib/color-picker/color-picker.module.ts +++ b/packages/bits/src/lib/color-picker/color-picker.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; -import { NuiCommonModule } from "../../common/common.module"; import { ColorPickerComponent } from "./color-picker.component"; -import { NuiSelectV2Module } from "../select-v2/select-v2.module"; +import { NuiCommonModule } from "../../common/common.module"; import { NuiIconModule } from "../icon/icon.module"; +import { NuiSelectV2Module } from "../select-v2/select-v2.module"; /** * @ignore diff --git a/packages/bits/src/lib/color-picker/color.service.ts b/packages/bits/src/lib/color-picker/color.service.ts index 854d0fe26..8789b79b5 100644 --- a/packages/bits/src/lib/color-picker/color.service.ts +++ b/packages/bits/src/lib/color-picker/color.service.ts @@ -20,6 +20,7 @@ import { Injectable } from "@angular/core"; import isNil from "lodash/isNil"; + import { HTML_COLORS } from "./../../constants/color-picker.constants"; diff --git a/packages/bits/src/lib/image/image.component.ts b/packages/bits/src/lib/image/image.component.ts index b9eabb83b..6d70b36de 100644 --- a/packages/bits/src/lib/image/image.component.ts +++ b/packages/bits/src/lib/image/image.component.ts @@ -42,9 +42,9 @@ import _isUndefined from "lodash/isUndefined"; import { IImagesPresetItem } from "./public-api"; import { imagesPresetToken } from "../../constants/images.constants"; +import { computeA11yForGraphic } from "../../functions/a11y-graphics.util"; import { LoggerService } from "../../services/log-service"; import { UtilService } from "../../services/util.service"; -import { computeA11yForGraphic } from "../../functions/a11y-graphics.util"; /** * ./../examples/index.html#/image diff --git a/packages/bits/src/lib/search/search.component.ts b/packages/bits/src/lib/search/search.component.ts index 647005033..d2dcb3ff1 100644 --- a/packages/bits/src/lib/search/search.component.ts +++ b/packages/bits/src/lib/search/search.component.ts @@ -130,8 +130,7 @@ export class SearchComponent implements IFilterPub, OnDestroy, OnInit { } public ngOnInit(): void { this.resolvedInputId = - `nui-search-input-${SearchComponent.nextUniqueId++}` || - this.inputId; + this.inputId || `nui-search-input-${SearchComponent.nextUniqueId++}`; } public getFilters(): IFilter { From 42ed6da7aa9dfbfaf985af8d10c3fe6715912e09 Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 15 Jan 2026 13:42:42 +0100 Subject: [PATCH 12/23] NUI-6263 ng v17 upgrade of the Image component --- .../bits/src/lib/image/image.component.html | 18 +- .../src/lib/image/image.component.spec.ts | 57 ++--- .../bits/src/lib/image/image.component.ts | 224 ++++++++---------- packages/bits/src/lib/image/image.module.ts | 3 +- .../bits/src/lib/search/search.component.ts | 3 - 5 files changed, 138 insertions(+), 167 deletions(-) diff --git a/packages/bits/src/lib/image/image.component.html b/packages/bits/src/lib/image/image.component.html index 52761a13a..e19e11a3e 100644 --- a/packages/bits/src/lib/image/image.component.html +++ b/packages/bits/src/lib/image/image.component.html @@ -1,12 +1,12 @@
diff --git a/packages/bits/src/lib/image/image.component.spec.ts b/packages/bits/src/lib/image/image.component.spec.ts index 1a6d637ce..509359db5 100644 --- a/packages/bits/src/lib/image/image.component.spec.ts +++ b/packages/bits/src/lib/image/image.component.spec.ts @@ -18,18 +18,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import { ChangeDetectorRef, ElementRef } from "@angular/core"; -import { TestBed } from "@angular/core/testing"; -import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { SafeHtml } from "@angular/platform-browser"; import _sample from "lodash/sample"; import { ImageComponent } from "./image.component"; import { IImagesPresetItem } from "./public-api"; +import { imagesPresetToken } from "../../constants/images.constants"; import { LoggerService } from "../../services/log-service"; import { UtilService } from "../../services/util.service"; +function unwrapSafeHtml(safeHtml: SafeHtml): string { + return (safeHtml as { changingThisBreaksApplicationSecurity: string }).changingThisBreaksApplicationSecurity; +} + describe("components >", () => { describe("image >", () => { + let fixture: ComponentFixture; let subject: ImageComponent; const imagesPreset = [ { @@ -45,44 +50,32 @@ describe("components >", () => { code: "test svg", }, ] as Array; - const domSanitizer = { - bypassSecurityTrustHtml: (code: string) => code as SafeHtml, - } as DomSanitizer; - const elRef: ElementRef = new ElementRef(` - -
- -
-
- `); beforeEach(() => { TestBed.configureTestingModule({ - providers: [UtilService, ChangeDetectorRef], + imports: [ImageComponent], + providers: [ + UtilService, + LoggerService, + { provide: imagesPresetToken, useValue: imagesPreset }, + ], }); - const utilService = TestBed.inject(UtilService); - const changeDetector = TestBed.inject(ChangeDetectorRef); - - subject = new ImageComponent( - new LoggerService(), - utilService, - changeDetector, - imagesPreset, - domSanitizer, - elRef - ); + fixture = TestBed.createComponent(ImageComponent); + subject = fixture.componentInstance; }); describe("getImageTemplate", () => { it("returns image code by given image name", () => { const image = _sample(imagesPreset); const imageName = image?.name; - const expectedImageCode = image?.code as SafeHtml; + const expectedImageCode = image?.code as string; - subject.image = imageName; + fixture.componentRef.setInput("image", imageName); + fixture.detectChanges(); - expect(subject.getImageTemplate()).toBe(expectedImageCode); + const result = unwrapSafeHtml(subject.imageTemplate()); + expect(result).toContain(expectedImageCode); }); it("returns default image template if there is no image in the preset", () => { @@ -90,10 +83,12 @@ describe("components >", () => { const description = "Unavailable image"; const expectedImageTemplate = `${description}`; - subject.image = imageName; - subject.description = description; + fixture.componentRef.setInput("image", imageName); + fixture.componentRef.setInput("description", description); + fixture.detectChanges(); - expect(subject.getImageTemplate()).toBe(expectedImageTemplate); + const result = unwrapSafeHtml(subject.imageTemplate()); + expect(result).toContain(expectedImageTemplate); }); }); }); diff --git a/packages/bits/src/lib/image/image.component.ts b/packages/bits/src/lib/image/image.component.ts index 6d70b36de..ef82ed7fc 100644 --- a/packages/bits/src/lib/image/image.component.ts +++ b/packages/bits/src/lib/image/image.component.ts @@ -19,26 +19,18 @@ // THE SOFTWARE. import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, - Inject, - Input, - OnChanges, - OnInit, - SimpleChanges, ViewEncapsulation, + afterNextRender, + computed, + effect, + inject, + input, } from "@angular/core"; import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; -import _find from "lodash/find"; -import _has from "lodash/has"; -import _includes from "lodash/includes"; -import _isEqual from "lodash/isEqual"; -import _isNumber from "lodash/isNumber"; -import _isString from "lodash/isString"; -import _isUndefined from "lodash/isUndefined"; import { IImagesPresetItem } from "./public-api"; import { imagesPresetToken } from "../../constants/images.constants"; @@ -51,167 +43,155 @@ import { UtilService } from "../../services/util.service"; */ @Component({ selector: "nui-image", + standalone: true, templateUrl: "./image.component.html", changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ["./image.component.less"], encapsulation: ViewEncapsulation.None, host: { - "[attr.role]": "computedRole", - "[attr.aria-hidden]": "computedAriaHidden", - "[attr.aria-label]": "computedAriaLabel", + "[attr.role]": "computedRole()", + "[attr.aria-hidden]": "computedAriaHidden()", + "[attr.aria-label]": "computedAriaLabel()", }, standalone: false, }) -export class ImageComponent implements OnInit, AfterViewInit, OnChanges { +export class ImageComponent { /** * Image name from nui image preset or external source */ - @Input() public image: any; + public image = input(); /** * Sets aria-label text or alt for image from external source */ - @Input() public description: string; + public description = input(); /** * Available values are: 'left' and 'right' */ - @Input() public float: string; + public float = input(); /** * Available values are: 'centered', 'small', 'large' */ - @Input() public margin: string; + public margin = input(); /** * 'True' will apply 30% opacity to the image */ - @Input() public isWatermark: boolean; + public isWatermark = input(); /** * Sets the width of the container parent image */ - @Input() public width: string = "auto"; + public width = input("auto"); /** * Sets the height of the container parent image */ - @Input() public height: string = "auto"; + public height = input("auto"); /** * When set to true sets the hardcoded width and height of the svg to 100% to fill the parent container */ - @Input() public autoFill: boolean; - - public imageTemplate: SafeHtml; - public imageName: string | null; - public hasAlt: boolean; - + public autoFill = input(); /** * Optional ARIA role override. Constrained to 'img' | 'presentation' | null. * If omitted, role is inferred from presence of alt/label. */ - @Input() public role?: "img" | "presentation" | null; - - constructor( - private logger: LoggerService, - private utilService: UtilService, - private changeDetector: ChangeDetectorRef, - @Inject(imagesPresetToken) private images: Array, - private domSanitizer: DomSanitizer, - private el: ElementRef - ) {} - - public ngOnInit(): void { - const dimensionImputs: string[] = [this.height, this.width]; - - dimensionImputs.forEach((item) => { - if (!_isUndefined(item) && !this.isImageSizeValid(item)) { - this.logger.error( - "Image size should be specified in 'px', '%', or 'auto" - ); - } - }); - } + public role = input<"img" | "presentation" | null>(); - public ngOnChanges(changes: SimpleChanges): void { - if (changes.image || changes.description) { - this.imageName = - this.image.name || this.getImage(this.image)?.name || null; - this.imageTemplate = this.getImageTemplate(); + public imageName = computed(() => { + const img = this.image(); + if (img && typeof img === "object" && "name" in img) { + return (img as any).name ?? null; } - } - - // --- Accessibility computed properties --- - private get a11y() { - return computeA11yForGraphic({ - decorative: false, // images default to semantic unless lacking label/alt - explicitRole: this.role, - label: this.description || this.imageName || null, - hasAlt: this.hasAlt, + const fromPreset = this.getImage(img as string); + return fromPreset?.name ?? null; + }); + + public hasAlt = computed(() => { + const img = this.image(); + const hasCode = !!(img && typeof img === "object" && typeof (img as any).code === "string"); + return !hasCode; + }); + + public imageTemplate = computed(() => { + const img = this.image(); + let html = ""; + if (img && typeof img === "object" && typeof (img as any).code === "string") { + html = (img as any).code; + } else if (typeof img === "string") { + const fromPreset = this.getImage(img); + if (fromPreset?.code) { + html = fromPreset.code; + } else { + html = `${this.description()}`; + } + } + return this.domSanitizer.bypassSecurityTrustHtml(html); + }); + + private a11y = computed(() => + computeA11yForGraphic({ + decorative: false, + explicitRole: this.role(), + label: this.description() || this.imageName() || null, + hasAlt: this.hasAlt(), statusParts: undefined, + }) + ); + + private logger = inject(LoggerService); + private utilService = inject(UtilService); + private changeDetector = inject(ChangeDetectorRef); + private images = inject>(imagesPresetToken); + private domSanitizer = inject(DomSanitizer); + private el = inject(ElementRef); + + public computedRole = computed(() => this.a11y().role); + public computedAriaHidden = computed(() => this.a11y().ariaHidden); + public computedAriaLabel = computed(() => this.a11y().ariaLabel); + + constructor() { + effect(() => { + const h = this.height(); + const w = this.width(); + [h, w].forEach((item) => { + if (item !== undefined && !this.isImageSizeValid(item)) { + this.logger.error( + "Image size should be specified in 'px', '%', or 'auto" + ); + } + }); }); - } - public get computedRole(): string | null { - return this.a11y.role; - } - public get computedAriaHidden(): string | null { - return this.a11y.ariaHidden; - } - public get computedAriaLabel(): string | null { - return this.a11y.ariaLabel; - } - - public ngAfterViewInit(): void { - if (this.autoFill) { - try { + afterNextRender(() => { + if (this.autoFill()) { const svg = this.el.nativeElement.querySelector("svg"); - svg.setAttribute("width", "100%"); - svg.setAttribute("height", "100%"); - } catch { - console.warn( - "Can't apply 'autoFill' to nui-image, because it is only applicable to SVG type of images" - ); - return; + if (svg) { + svg.setAttribute("width", "100%"); + svg.setAttribute("height", "100%"); + } else { + console.warn( + "Can't apply 'autoFill' to nui-image, because it is only applicable to SVG type of images" + ); + } } - } - - /** - * Fix bug for Safari with wrong alignment of floated SVG images - */ - if (this.float && !this.width && this.utilService.browser?.isSafari()) { - const svg = this.el.nativeElement.querySelector("svg"); - if (!svg) { - return; + // Fix bug for Safari with wrong alignment of floated SVG images + if (this.float() && !this.width() && this.utilService.browser?.isSafari()) { + const svg = this.el.nativeElement.querySelector("svg"); + if (svg) { + (this.el.nativeElement as HTMLElement).style.width = svg.width.baseVal.value + "px"; + this.changeDetector.detectChanges(); + } } - - this.width = svg.width.baseVal.value + "px"; - this.changeDetector.detectChanges(); - } - } - - public getImageTemplate(): SafeHtml { - const image = this.image.code ? this.image : this.getImage(this.image); - let imageHtml: string = ""; - if (_has(image, "code") && _isString(image.code)) { - imageHtml = image.code; - this.hasAlt = false; - } else { - imageHtml = `${this.description}`; - this.hasAlt = true; - } - - return this.domSanitizer.bypassSecurityTrustHtml(imageHtml); + }); } private getImage = (imageName: string): IImagesPresetItem | undefined => - _find(this.images, (img: IImagesPresetItem) => - _isEqual(img.name, imageName) - ); + this.images.find((img) => img.name === imageName); - private isImageSizeValid(input: string): boolean { + private isImageSizeValid(value: string): boolean { return ( - _isNumber(parseFloat(input)) && - (_includes(input, "px") || - _includes(input, "%") || - _includes(input, "auto")) + !isNaN(parseFloat(value)) && + (value.includes("px") || value.includes("%") || value.includes("auto")) ); } } diff --git a/packages/bits/src/lib/image/image.module.ts b/packages/bits/src/lib/image/image.module.ts index 3f90794cd..48b226272 100644 --- a/packages/bits/src/lib/image/image.module.ts +++ b/packages/bits/src/lib/image/image.module.ts @@ -27,8 +27,7 @@ import { NuiCommonModule } from "../../common/common.module"; * @ignore */ @NgModule({ - declarations: [ImageComponent], - imports: [NuiCommonModule], + imports: [NuiCommonModule, ImageComponent], exports: [ImageComponent], providers: [], }) diff --git a/packages/bits/src/lib/search/search.component.ts b/packages/bits/src/lib/search/search.component.ts index d2dcb3ff1..6deefa9ee 100644 --- a/packages/bits/src/lib/search/search.component.ts +++ b/packages/bits/src/lib/search/search.component.ts @@ -176,9 +176,6 @@ export class SearchComponent implements IFilterPub, OnDestroy, OnInit { this.search.emit(this.value); } - /* -------------------------------------------------- - Lifecycle teardown - -------------------------------------------------- */ public ngOnDestroy(): void { this.onDestroy$.next(); this.onDestroy$.complete(); From 278a39ce62f7fe8e447cb1eac94f66bef3f3895e Mon Sep 17 00:00:00 2001 From: Ludmila Fialova Date: Thu, 15 Jan 2026 17:15:28 +0100 Subject: [PATCH 13/23] NUI-6263 ng v17 upgrade of the Search component --- .../bits/src/lib/search/search.component.html | 43 +++---- .../src/lib/search/search.component.spec.ts | 42 +++---- .../bits/src/lib/search/search.component.ts | 109 ++++++++---------- packages/bits/src/lib/search/search.module.ts | 8 +- 4 files changed, 86 insertions(+), 116 deletions(-) diff --git a/packages/bits/src/lib/search/search.component.html b/packages/bits/src/lib/search/search.component.html index ae21512f4..30a1680d9 100644 --- a/packages/bits/src/lib/search/search.component.html +++ b/packages/bits/src/lib/search/search.component.html @@ -1,43 +1,44 @@ -
+
- + @if (value()) { + + }