From 0b709ac4d19270a65727caa88898d99e2636d195 Mon Sep 17 00:00:00 2001 From: "Bajohr, Rayk" Date: Fri, 3 Jul 2026 14:28:16 +0200 Subject: [PATCH] refactor(live-preview): migrate live-preview to signals BREAKING CHANGE: The live-preview components were migrated to signal inputs. --- api-goldens/live-preview/index.api.md | 196 +++++++------- .../live-preview/webcomponents/index.api.md | 20 +- .../si-example-viewer.component.html | 34 +-- .../si-example-viewer.component.ts | 130 ++++----- .../si-live-preview-iframe.component.html | 57 ++-- .../si-live-preview-iframe.component.ts | 226 ++++++++-------- .../si-live-preview-qr.component.html | 8 +- .../si-live-preview-qr.component.ts | 43 ++- .../si-live-preview-renderer.component.ts | 119 +++++---- .../si-live-preview-web.component.ts | 31 ++- .../si-live-preview-wrapper.component.html | 6 +- .../si-live-preview-wrapper.component.ts | 61 +++-- .../si-live-preview.component.html | 60 +++-- .../si-live-preview.component.ts | 249 ++++++++++-------- 14 files changed, 652 insertions(+), 588 deletions(-) diff --git a/api-goldens/live-preview/index.api.md b/api-goldens/live-preview/index.api.md index bccd36eca5..f3e224f38b 100644 --- a/api-goldens/live-preview/index.api.md +++ b/api-goldens/live-preview/index.api.md @@ -6,8 +6,8 @@ import { ActivatedRoute } from '@angular/router'; import { AfterViewInit } from '@angular/core'; +import * as _angular_core from '@angular/core'; import { ElementRef } from '@angular/core'; -import * as i0 from '@angular/core'; import * as i1 from '@angular/router'; import { InjectionToken } from '@angular/core'; import { ModuleWithProviders } from '@angular/core'; @@ -80,43 +80,43 @@ export class SiExampleViewerComponent { // (undocumented) activateTab(index: number): void; // (undocumented) - activeTabIndex: number; + readonly activeTabIndex: _angular_core.WritableSignal; // (undocumented) - baseUrl: string; + readonly baseUrl: string; // (undocumented) - dataId: string; + readonly dataId: _angular_core.WritableSignal; // (undocumented) - exampleUrl: string; + readonly exampleUrl: _angular_core.WritableSignal; // (undocumented) - get hasTabs(): boolean; + readonly hasTabs: _angular_core.Signal; // (undocumented) - get hasTabsMobile(): boolean; + readonly hasTabsMobile: _angular_core.Signal; // (undocumented) - isRTL: boolean; + readonly isRTL: _angular_core.WritableSignal; // (undocumented) - jsTemplate: string; + readonly jsTemplate: _angular_core.WritableSignal; // (undocumented) - locale?: string; + readonly locale: _angular_core.WritableSignal; // (undocumented) - mode: string; + readonly mode: _angular_core.WritableSignal; // (undocumented) - reactTemplate: string; + readonly reactTemplate: _angular_core.WritableSignal; // (undocumented) - readonly renderer: i0.Signal; + readonly renderer: _angular_core.Signal; // (undocumented) - tabs: { + readonly tabs: _angular_core.WritableSignal<{ heading: string; base: string; example: string; - }[]; + }[]>; // (undocumented) - template: string; + readonly template: _angular_core.WritableSignal; // (undocumented) - theme: string; + readonly theme: _angular_core.WritableSignal; // (undocumented) - ticketBaseUrl: string; + readonly ticketBaseUrl: string; // (undocumented) - vueTemplate: string; + readonly vueTemplate: _angular_core.WritableSignal; } // @public (undocumented) @@ -125,7 +125,7 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) activateTab(tab: string): void; // (undocumented) - activeTab: string; + readonly activeTab: _angular_core.WritableSignal; // (undocumented) allowCopy: boolean; // (undocumented) @@ -133,15 +133,15 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) availableLocales: string[]; // (undocumented) - baseUrl: string; + readonly baseUrl: _angular_core.InputSignal; // (undocumented) changeFramework(framework: string): void; // (undocumented) - changeLocale(locale: string): void; + changeLocale(locale: string | null | undefined): void; // (undocumented) checkWebComponentsAvailable(): void; // (undocumented) - readonly consoleElem: i0.Signal>; + readonly consoleElem: _angular_core.Signal>; // (undocumented) copyCode(): void; // (undocumented) @@ -151,29 +151,29 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) editorCollapsed: boolean; // (undocumented) - example?: string | null; + readonly example: _angular_core.InputSignal; // (undocumented) exampleFullscreen: boolean; // (undocumented) - frameworks: Map; + readonly frameworks: Map; // (undocumented) handleInProgressEvent(inProgress: boolean): void; // (undocumented) inProgress: boolean; // (undocumented) - isFullscreen: boolean; + readonly isFullscreen: _angular_core.WritableSignal; // (undocumented) isMobile: boolean; // (undocumented) - isRTL: boolean; + readonly isRTL: _angular_core.ModelSignal; // (undocumented) - loadJs: boolean; + readonly loadJs: _angular_core.WritableSignal; // (undocumented) - loadReact: boolean; + readonly loadReact: _angular_core.WritableSignal; // (undocumented) - loadVue: boolean; + readonly loadVue: _angular_core.WritableSignal; // (undocumented) - locale?: string; + readonly locale: _angular_core.ModelSignal; // (undocumented) localeApi: SiLivePreviewLocaleApi | null; // (undocumented) @@ -191,17 +191,17 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) openTab(): void; // (undocumented) - readonly reactVueElem: i0.Signal>; + readonly reactVueElem: _angular_core.Signal>; // (undocumented) renderingError: any; // (undocumented) rfsSelectionChanges(value: string): void; // (undocumented) - rootFontSize: number | 'initial'; + readonly rootFontSize: _angular_core.WritableSignal; // (undocumented) - rootFontSizes: number[]; + readonly rootFontSizes: number[]; // (undocumented) - rtlSwitcher: boolean | undefined; + readonly rtlSwitcher: boolean | undefined; // (undocumented) selectedFramework: string; // (undocumented) @@ -209,27 +209,27 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) showEditor: boolean; // (undocumented) - switcherEnabled: boolean | undefined; + readonly switcherEnabled: boolean | undefined; // (undocumented) - template: string; + readonly template: _angular_core.ModelSignal; // (undocumented) - readonly templateElem: i0.Signal>; + readonly templateElem: _angular_core.Signal>; // (undocumented) templateFromComponent(template?: string): void; // (undocumented) - templateJs: string; + readonly templateJs: _angular_core.ModelSignal; // (undocumented) - templateReact: string; + readonly templateReact: _angular_core.ModelSignal; // (undocumented) templateTs: string; // (undocumented) - templateVue: string; + readonly templateVue: _angular_core.ModelSignal; // (undocumented) - theme: string; + readonly theme: _angular_core.ModelSignal; // (undocumented) themeChange(theme: string): void; // (undocumented) - ticketBaseUrl: string; + readonly ticketBaseUrl: _angular_core.InputSignal; // (undocumented) ticketLinkBug: string; // (undocumented) @@ -243,9 +243,9 @@ export class SiLivePreviewComponent implements OnInit, AfterViewInit, OnChanges // (undocumented) toggleTheme(): void; // (undocumented) - readonly typescriptElem: i0.Signal>; + readonly typescriptElem: _angular_core.Signal>; // (undocumented) - webcomponents: boolean | undefined; + readonly webcomponents: boolean | undefined; } // @public @@ -277,59 +277,56 @@ export interface SiLivePreviewConfig { } // @public (undocumented) -export class SiLivePreviewIframeComponent implements OnInit, OnChanges { +export class SiLivePreviewIframeComponent implements OnInit { + constructor(); // (undocumented) - baseUrl: string; + readonly baseUrl: _angular_core.InputSignal; // (undocumented) deviceChanged(): void; // (undocumented) - exampleUrl: string; + readonly exampleUrl: _angular_core.InputSignal; // (undocumented) - iFrameHeight?: string; + readonly iFrameHeight: _angular_core.InputSignal; // (undocumented) - iFrameWidth?: string; + readonly iFrameWidth: _angular_core.InputSignal; // (undocumented) - readonly inProgress: i0.OutputEmitterRef; + readonly inProgress: _angular_core.OutputEmitterRef; // (undocumented) - isFullscreen: boolean; + readonly isFullscreen: _angular_core.InputSignal; // (undocumented) - isRTL?: boolean; + readonly isRTL: _angular_core.InputSignal; // (undocumented) - loadJs?: boolean; + readonly loadJs: _angular_core.InputSignal; // (undocumented) - loadReact?: boolean; + readonly loadReact: _angular_core.InputSignal; // (undocumented) - loadVue?: boolean; + readonly loadVue: _angular_core.InputSignal; // (undocumented) - locale?: string; + readonly locale: _angular_core.ModelSignal; // (undocumented) - readonly localeChange: i0.OutputEmitterRef; + readonly logClear: _angular_core.OutputEmitterRef; // (undocumented) - readonly logClear: i0.OutputEmitterRef; + readonly logMessage: _angular_core.OutputEmitterRef; // (undocumented) - readonly logMessage: i0.OutputEmitterRef; - // (undocumented) - readonly logRenderingError: i0.OutputEmitterRef; + readonly logRenderingError: _angular_core.OutputEmitterRef; // (undocumented) openQrMenu(): void; // (undocumented) - readonly previewIframe: i0.Signal | undefined>; + readonly previewIframe: _angular_core.Signal | undefined>; // (undocumented) - reactVueTemplate?: string; + readonly reactVueTemplate: _angular_core.InputSignal; // (undocumented) - rootFontSize: number | 'initial'; + readonly rootFontSize: _angular_core.InputSignal; // (undocumented) - template: string; + readonly template: _angular_core.InputSignal; // (undocumented) - readonly templateFromComponent: i0.OutputEmitterRef; + readonly templateFromComponent: _angular_core.OutputEmitterRef; // (undocumented) - theme: string; + readonly theme: _angular_core.ModelSignal; // (undocumented) - readonly themeChange: i0.OutputEmitterRef; + readonly ticketLinkBug: _angular_core.InputSignal; // (undocumented) - ticketLinkBug: string; - // (undocumented) - ticketLinkFeature: string; + readonly ticketLinkFeature: _angular_core.InputSignal; // (undocumented) toggleLandscape(): void; // (undocumented) @@ -363,47 +360,46 @@ export { SiLivePreviewModule } export { SiLivePreviewModule as SimplLivePreviewModule } // @public (undocumented) -export class SiLivePreviewQrComponent implements AfterViewInit, OnDestroy, OnChanges { +export class SiLivePreviewQrComponent implements AfterViewInit, OnDestroy { // (undocumented) - readonly closed: i0.OutputEmitterRef; + readonly closed: _angular_core.OutputEmitterRef; // (undocumented) - qrImg: string; + readonly qrImg: _angular_core.Signal; // (undocumented) - qrShort: boolean; + readonly qrShort: _angular_core.Signal; // (undocumented) - url?: string; + readonly url: _angular_core.InputSignal; // (undocumented) - urlShort?: string; + readonly urlShort: _angular_core.InputSignal; } // @public (undocumented) -export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { +export class SiLivePreviewRendererComponent implements OnDestroy { + constructor(); // (undocumented) - dataId: string; + readonly dataId: _angular_core.InputSignal; // (undocumented) - exampleUrl: string; + readonly exampleUrl: _angular_core.InputSignal; // (undocumented) - readonly inProgress: i0.OutputEmitterRef; + readonly inProgress: _angular_core.OutputEmitterRef; // (undocumented) - readonly logClear: i0.OutputEmitterRef; + readonly logClear: _angular_core.OutputEmitterRef; // (undocumented) - readonly logMessage: i0.OutputEmitterRef; + readonly logMessage: _angular_core.OutputEmitterRef; // (undocumented) - readonly logRenderingError: i0.OutputEmitterRef; + readonly logRenderingError: _angular_core.OutputEmitterRef; // (undocumented) - readonly react: i0.Signal>; + readonly react: _angular_core.Signal>; // (undocumented) recompile(): void; // (undocumented) - readonly renderedExample: i0.Signal; - // (undocumented) - renderingDone: boolean; + readonly renderedExample: _angular_core.Signal; // (undocumented) - readonly supportsLandscapeMode: i0.OutputEmitterRef; + readonly supportsLandscapeMode: _angular_core.OutputEmitterRef; // (undocumented) - template: string; + readonly template: _angular_core.InputSignal; // (undocumented) - readonly templateFromComponent: i0.OutputEmitterRef; + readonly templateFromComponent: _angular_core.OutputEmitterRef; } // @public (undocumented) @@ -432,25 +428,25 @@ export abstract class SiLivePreviewWebComponentService { export class SiLivePreviewWrapperComponent { constructor(); // (undocumented) - exampleUrl: string; + readonly exampleUrl: _angular_core.WritableSignal; // (undocumented) - loadJs: boolean; + readonly loadJs: _angular_core.WritableSignal; // (undocumented) - loadReact: boolean; + readonly loadReact: _angular_core.WritableSignal; // (undocumented) - loadVue: boolean; + readonly loadVue: _angular_core.WritableSignal; // (undocumented) onClick(event: MouseEvent): void; // (undocumented) - readonly renderer: i0.Signal; + readonly renderer: _angular_core.Signal; // (undocumented) sendMessage(type: string, message?: any): void; // (undocumented) - template: string; + readonly template: _angular_core.WritableSignal; // (undocumented) - readonly webcomponentRenderer: i0.Signal>; + readonly webcomponentRenderer: _angular_core.Signal>; // (undocumented) - webcomponentTemplateCode: string; + readonly webcomponentTemplateCode: _angular_core.WritableSignal; } // @public diff --git a/api-goldens/live-preview/webcomponents/index.api.md b/api-goldens/live-preview/webcomponents/index.api.md index 0d35237c09..53028b057d 100644 --- a/api-goldens/live-preview/webcomponents/index.api.md +++ b/api-goldens/live-preview/webcomponents/index.api.md @@ -4,8 +4,8 @@ ```ts +import * as _angular_core from '@angular/core'; import { ElementRef } from '@angular/core'; -import * as i0 from '@angular/core'; import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { SimpleChanges } from '@angular/core'; @@ -13,29 +13,29 @@ import { SimpleChanges } from '@angular/core'; // @public (undocumented) export class SiLivePreviewWebComponent implements OnChanges { // (undocumented) - config: SiLivePreviewConfig; + readonly config: _angular_core.InputSignal; // (undocumented) - exampleUrl: string; + readonly exampleUrl: _angular_core.InputSignal; // (undocumented) - readonly inProgress: i0.OutputEmitterRef; + readonly inProgress: _angular_core.OutputEmitterRef; // (undocumented) loadHTMLFromCodeTemplate(code: string): void; // (undocumented) - loadJs: boolean; + readonly loadJs: _angular_core.InputSignal; // (undocumented) - loadReact: boolean; + readonly loadReact: _angular_core.InputSignal; // (undocumented) loadReactFromCodeTemplate(code: string): void; // (undocumented) - loadVue: boolean; + readonly loadVue: _angular_core.InputSignal; // (undocumented) loadVueFromCodeTemplate(code: string): void; // (undocumented) - renderingDone: boolean; + readonly renderingDone: _angular_core.WritableSignal; // (undocumented) - readonly root: i0.Signal>; + readonly root: _angular_core.Signal>; // (undocumented) - webcomponentTemplateCode: string; + readonly webcomponentTemplateCode: _angular_core.InputSignal; } // @public diff --git a/projects/live-preview/components/si-example-viewer/si-example-viewer.component.html b/projects/live-preview/components/si-example-viewer/si-example-viewer.component.html index 6eaea25b0f..c84c111b3d 100644 --- a/projects/live-preview/components/si-example-viewer/si-example-viewer.component.html +++ b/projects/live-preview/components/si-example-viewer/si-example-viewer.component.html @@ -1,26 +1,26 @@
- @if (!exampleUrl && !template) { + @if (!exampleUrl() && !template()) {

No example specified. Head to /overview for a list of them.

} - @if ((template || reactTemplate || vueTemplate || jsTemplate) && mode === 'editor') { + @if ((template() || reactTemplate() || vueTemplate() || jsTemplate()) && mode() === 'editor') {

This is an adopted example built from the URL. Head to /overview for a list of examples.

} - @if (tabs.length > 1) { + @if (tabs().length > 1) {
-@if (mode !== 'editor') { +@if (mode() !== 'editor') { } @else { } diff --git a/projects/live-preview/components/si-example-viewer/si-example-viewer.component.ts b/projects/live-preview/components/si-example-viewer/si-example-viewer.component.ts index 8d96d03e81..0833fd6aca 100644 --- a/projects/live-preview/components/si-example-viewer/si-example-viewer.component.ts +++ b/projects/live-preview/components/si-example-viewer/si-example-viewer.component.ts @@ -2,7 +2,14 @@ * Copyright (c) Siemens 2016 - 2026 * SPDX-License-Identifier: MIT */ -import { ChangeDetectionStrategy, Component, HostBinding, inject, viewChild } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + signal, + viewChild +} from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { setDeviceMode, setDirectionRtl, setRootFontSize } from '../../helpers/utils'; @@ -23,43 +30,41 @@ import { SiLivePreviewComponent } from '../si-live-preview/si-live-preview.compo imports: [SiLivePreviewComponent, SiLivePreviewRendererComponent], templateUrl: './si-example-viewer.component.html', styleUrl: './si-example-viewer.component.scss', - changeDetection: ChangeDetectionStrategy.Eager + changeDetection: ChangeDetectionStrategy.Eager, + host: { + '[class.has-tabs]': 'hasTabs()', + '[class.has-tabs-mobile]': 'hasTabsMobile()' + } }) export class SiExampleViewerComponent { - private router = inject(Router); - private route = inject(ActivatedRoute); - private config = inject(SI_LIVE_PREVIEW_CONFIG); - private internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); - private themeApi = inject(SiLivePreviewThemeApi, { optional: true }); - private localeApi = inject(SiLivePreviewLocaleApi, { optional: true }); + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly config = inject(SI_LIVE_PREVIEW_CONFIG); + private readonly internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); + private readonly themeApi = inject(SiLivePreviewThemeApi, { optional: true }); + private readonly localeApi = inject(SiLivePreviewLocaleApi, { optional: true }); readonly renderer = viewChild.required('renderer'); - - @HostBinding('class.has-tabs') get hasTabs(): boolean { - return this.tabs.length > 1; - } - @HostBinding('class.has-tabs-mobile') get hasTabsMobile(): boolean { - return this.tabs.length > 1 && this.internalConfig.isMobile; - } - - ticketBaseUrl = this.config.ticketBaseUrl; - baseUrl = this.config.examplesBaseUrl; - exampleUrl!: string; - dataId = ''; - mode = 'viewer'; - theme = 'light'; - locale?: string; - isRTL = false; - template = ''; - reactTemplate = ''; - vueTemplate = ''; - jsTemplate = ''; - - tabs: { heading: string; base: string; example: string }[] = []; - activeTabIndex = 0; + readonly ticketBaseUrl = this.config.ticketBaseUrl; + readonly baseUrl = this.config.examplesBaseUrl; + readonly exampleUrl = signal(''); + readonly dataId = signal(''); + readonly mode = signal('viewer'); + readonly theme = signal('light'); + readonly locale = signal(''); + readonly isRTL = signal(false); + readonly template = signal(''); + readonly reactTemplate = signal(''); + readonly vueTemplate = signal(''); + readonly jsTemplate = signal(''); + + readonly tabs = signal<{ heading: string; base: string; example: string }[]>([]); + readonly hasTabs = computed(() => this.tabs().length > 1); + readonly hasTabsMobile = computed(() => this.tabs().length > 1 && this.internalConfig.isMobile); + readonly activeTabIndex = signal(0); constructor() { - this.route.params.subscribe(params => (this.mode = params.mode ?? 'editor')); + this.route.params.subscribe(params => this.mode.set(params.mode ?? 'editor')); this.route.queryParams.subscribe(params => this.handleQueryParams(params)); } @@ -69,36 +74,39 @@ export class SiExampleViewerComponent { const base = params.base ? params.base + '/' : ''; if (params.e) { - const prevUrl = this.exampleUrl; - this.tabs = []; - this.activeTabIndex = 0; - this.exampleUrl = ''; + const prevUrl = this.exampleUrl(); + this.tabs.set([]); + this.activeTabIndex.set(0); + this.exampleUrl.set(''); const examples = Array.isArray(params.e) ? params.e : [params.e]; examples.forEach(element => { const parts = element.split(';'); - this.tabs.push({ - heading: parts[1], - base, - example: parts[0] - }); + this.tabs.update(t => [ + ...t, + { + heading: parts[1], + base, + example: parts[0] + } + ]); }); - if (this.tabs.length) { + if (this.tabs().length) { this.activateTab(0); - recompile = prevUrl === this.exampleUrl; + recompile = prevUrl === this.exampleUrl(); } - if (this.tabs.length <= 1 && params.t) { + if (this.tabs().length <= 1 && params.t) { if (params.framework === 'react') { - this.reactTemplate = params.t; + this.reactTemplate.set(params.t); } else if (params.framework === 'vue') { - this.vueTemplate = params.t; + this.vueTemplate.set(params.t); } else if (params.framework === 'js') { - this.jsTemplate = params.t; + this.jsTemplate.set(params.t); } else { - recompile = recompile || this.template !== params.t; - this.template = params.t; + recompile = recompile || this.template() !== params.t; + this.template.set(params.t); } } @@ -120,7 +128,7 @@ export class SiExampleViewerComponent { this.setRTL(params.isRTL); handled = true; } - if (params.mode && this.mode !== 'editor') { + if (params.mode && this.mode() !== 'editor') { setDeviceMode(params.mode); recompile = true; handled = true; @@ -132,8 +140,8 @@ export class SiExampleViewerComponent { } if (params.id) { - recompile = this.dataId !== params.id; - this.dataId = params.id; + recompile = this.dataId() !== params.id; + this.dataId.set(params.id); } if (recompile) { @@ -147,8 +155,8 @@ export class SiExampleViewerComponent { } private setTheme(theme: ThemeType): void { - if (this.mode === 'editor') { - this.theme = theme; + if (this.mode() === 'editor') { + this.theme.set(theme); } if (this.themeApi) { @@ -158,15 +166,15 @@ export class SiExampleViewerComponent { } private setRTL(rtl: boolean): void { - this.isRTL = rtl; - if (this.mode !== 'editor') { + this.isRTL.set(rtl); + if (this.mode() !== 'editor') { setDirectionRtl(rtl); } } private setLocale(locale: string): void { - if (this.mode === 'editor') { - this.locale = locale; + if (this.mode() === 'editor') { + this.locale.set(locale); return; } @@ -176,8 +184,8 @@ export class SiExampleViewerComponent { } activateTab(index: number): void { - const example = this.tabs[index].example; - this.activeTabIndex = index; - this.exampleUrl = this.tabs[index].base + example; + const example = this.tabs()[index].example; + this.activeTabIndex.set(index); + this.exampleUrl.set(this.tabs()[index].base + example); } } diff --git a/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.html b/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.html index 621574617d..a1de4c21e0 100644 --- a/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.html +++ b/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.html @@ -1,16 +1,20 @@ @if (isMobile) {
- @for (device of availableDevices; track $index) {
- @if (allowFullscreen && !isFullscreen) { + @if (allowFullscreen && !isFullscreen()) {
@if (webcomponents && showEditor && frameworks.size > 1) {