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..f81ee4b451 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,9 +1,9 @@ @@ -22,10 +22,10 @@

Example: {{ exampleUrl }}

@@ -114,20 +113,20 @@

Example: {{ exampleUrl }}

- @if (mode !== 'ios') { + @if (mode() !== 'ios') {
- + - + @@ -139,28 +138,33 @@

Example: {{ exampleUrl }}

} - - @if (mode === 'ios' && showNotch) { -
+ @if (mode() === 'ios') { + @if (showNotch()) { + +
+
+
+
+
+
+ } @else { +
-
-
-
- } - - - @if (mode === 'ios' && !showNotch) { -
-
-
-
-
-
+
+
+
+
+ } }
- +
} @else { diff --git a/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.ts b/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.ts index b0da9a838c..b675a02fab 100644 --- a/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.ts +++ b/projects/live-preview/components/si-live-preview-iframe/si-live-preview-iframe.component.ts @@ -7,15 +7,16 @@ import { ChangeDetectorRef, Component, DestroyRef, + effect, ElementRef, - HostBinding, inject, - Input, + input, + linkedSignal, + model, NgZone, - OnChanges, OnInit, output, - SimpleChanges, + signal, viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -34,73 +35,77 @@ import { availableDevices, Device } from './devices'; imports: [FormsModule, SiLivePreviewQrComponent], templateUrl: './si-live-preview-iframe.component.html', styleUrl: './si-live-preview-iframe.component.scss', - changeDetection: ChangeDetectionStrategy.Eager + changeDetection: ChangeDetectionStrategy.Eager, + host: { + '[class.is-mobile]': 'isMobile' + } }) -export class SiLivePreviewIframeComponent implements OnInit, OnChanges { +export class SiLivePreviewIframeComponent implements OnInit { readonly previewIframe = viewChild('previewIframe'); - @Input() baseUrl!: string; - @Input() exampleUrl!: string; - @Input() template!: string; - @Input() ticketLinkBug!: string; - @Input() ticketLinkFeature!: string; - @Input() isFullscreen = false; - @Input() iFrameHeight?: string; - @Input() iFrameWidth?: string; - @Input() theme = 'light'; - @Input() locale?: string; - @Input() rootFontSize: number | 'initial' = 0; - @Input() isRTL?: boolean; - @Input() loadReact?: boolean; - @Input() loadVue?: boolean; - @Input() loadJs?: boolean; - @Input() reactVueTemplate?: string; + readonly baseUrl = input.required(); + readonly exampleUrl = input.required(); + readonly template = input.required(); + readonly ticketLinkBug = input.required(); + readonly ticketLinkFeature = input.required(); + readonly isFullscreen = input(false); + readonly iFrameHeight = input(); + readonly iFrameWidth = input(); + readonly theme = model('light'); + readonly locale = model(); + readonly rootFontSize = input(0); + readonly isRTL = input(); + readonly loadReact = input(); + readonly loadVue = input(); + readonly loadJs = input(); + readonly reactVueTemplate = input(); readonly templateFromComponent = output(); readonly logClear = output(); readonly logMessage = output(); readonly logRenderingError = output(); readonly inProgress = output(); - readonly themeChange = output(); - readonly localeChange = output(); private originalTemplate = ''; private templateModified = false; private rendererInProgress = false; - private config = inject(SI_LIVE_PREVIEW_CONFIG); - private internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); - private ngZone = inject(NgZone); - private destroyRef = inject(DestroyRef); - private cdRef = inject(ChangeDetectorRef); - - @HostBinding('class.is-mobile') protected isMobile = this.internalConfig.isMobile; + private readonly config = inject(SI_LIVE_PREVIEW_CONFIG); + private readonly internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); + private readonly ngZone = inject(NgZone); + private readonly destroyRef = inject(DestroyRef); + private readonly cdRef = inject(ChangeDetectorRef); - protected mode = 'ios'; - protected landscape = false; + protected readonly isMobile = this.internalConfig.isMobile; - protected switcherEnabled = this.config.themeSwitcher; + /** iframe height, seeded from the {@link iFrameHeight} input and mutable in mobile mode. */ + protected readonly currentIFrameHeight = linkedSignal(() => this.iFrameHeight()); + /** iframe width, seeded from the {@link iFrameWidth} input and mutable in mobile mode. */ + protected readonly currentIFrameWidth = linkedSignal(() => this.iFrameWidth()); - protected landscapeEnabled = this.config.landscapeToggle; - protected supportsLandscape = false; + protected readonly mode = signal('ios'); + protected readonly landscape = signal(false); - protected availableDevices = availableDevices; - protected selectedDevice?: Device; - protected showNotch!: boolean; - protected showQrMenu = false; - protected plainUrl = ''; - protected plainUrlShort = ''; + protected readonly switcherEnabled = this.config.themeSwitcher; - ngOnChanges(changes: SimpleChanges): void { - if (!this.previewIframe()) { - return; - } + protected readonly landscapeEnabled = this.config.landscapeToggle; + protected readonly supportsLandscape = signal(false); - if (changes.template) { - this.templateModified = this.originalTemplate !== this.template; - } + protected readonly availableDevices = availableDevices; + protected readonly selectedDevice = signal(undefined); + protected readonly showNotch = signal(false); + protected readonly showQrMenu = signal(false); + protected readonly plainUrl = signal(''); + protected readonly plainUrlShort = signal(''); - this.sendMessage(); + constructor() { + effect(() => { + // recompute on template changes and re-send whenever any rendered input changes + this.templateModified = this.originalTemplate !== this.template(); + if (this.previewIframe()) { + this.sendMessage(); + } + }); } ngOnInit(): void { @@ -115,19 +120,25 @@ export class SiLivePreviewIframeComponent implements OnInit, OnChanges { if (this.isMobile) { const deviceId = localStorage.getItem('si-live-preview-selected-device'); - this.selectedDevice = - this.availableDevices.find(dev => dev.id === deviceId) ?? this.availableDevices[0]; + this.selectedDevice.set( + this.availableDevices.find(dev => dev.id === deviceId) ?? this.availableDevices[0] + ); this.deviceChanged(); } } deviceChanged(): void { - this.selectedDevice ??= this.availableDevices[0]; - this.iFrameHeight = this.landscape ? this.selectedDevice.width : this.selectedDevice.height; - this.iFrameWidth = this.landscape ? this.selectedDevice.height : this.selectedDevice.width; - this.showNotch = this.selectedDevice.notch ?? false; - this.mode = this.selectedDevice.mode; - localStorage.setItem('si-live-preview-selected-device', this.selectedDevice.id); + let selectedDevice = this.selectedDevice(); + if (!selectedDevice) { + selectedDevice = this.availableDevices[0]; + this.selectedDevice.set(selectedDevice); + } + const landscape = this.landscape(); + this.currentIFrameHeight.set(landscape ? selectedDevice.width : selectedDevice.height); + this.currentIFrameWidth.set(landscape ? selectedDevice.height : selectedDevice.width); + this.showNotch.set(selectedDevice.notch ?? false); + this.mode.set(selectedDevice.mode); + localStorage.setItem('si-live-preview-selected-device', selectedDevice.id); this.sendMessage(); } @@ -145,9 +156,9 @@ export class SiLivePreviewIframeComponent implements OnInit, OnChanges { this.sendMessage(); break; case 'landscapeMode': - this.supportsLandscape = event.data.message; - if (!this.supportsLandscape && this.landscape) { - this.landscape = false; + this.supportsLandscape.set(event.data.message); + if (!this.supportsLandscape() && this.landscape()) { + this.landscape.set(false); this.deviceChanged(); } break; @@ -176,55 +187,56 @@ export class SiLivePreviewIframeComponent implements OnInit, OnChanges { this.templateFromComponent.emit(event.data.message); break; case 'theme': - this.theme = event.data.message; - this.themeChange.emit(event.data.message); + this.theme.set(event.data.message); break; case 'locale': - this.locale = event.data.message; - this.localeChange.emit(event.data.message); + this.locale.set(event.data.message); break; } } toggleTheme(): void { - this.theme = this.theme === 'dark' ? 'light' : 'dark'; - this.themeChange.emit(this.theme); + this.theme.set(this.theme() === 'dark' ? 'light' : 'dark'); this.sendMessage(); } toggleLandscape(): void { - this.landscape = !this.landscape; - this.iFrameHeight = this.landscape ? this.selectedDevice?.width : this.selectedDevice?.height; - this.iFrameWidth = this.landscape ? this.selectedDevice?.height : this.selectedDevice?.width; + const landscape = !this.landscape(); + this.landscape.set(landscape); + const selectedDevice = this.selectedDevice(); + this.currentIFrameHeight.set(landscape ? selectedDevice?.width : selectedDevice?.height); + this.currentIFrameWidth.set(landscape ? selectedDevice?.height : selectedDevice?.width); this.sendMessage(); } openQrMenu(): void { - this.plainUrl = this.createTemplateLink('plain'); - this.plainUrlShort = this.createTemplateLink('plain', true); - this.showQrMenu = true; + this.plainUrl.set(this.createTemplateLink('plain')); + this.plainUrlShort.set(this.createTemplateLink('plain', true)); + this.showQrMenu.set(true); } private createTemplateLink(mode: string, skipTemplate = false): string { let url = `${window.location.protocol}//${window.location.host}`; url += window.location.pathname; url += `#/viewer/${mode}?`; - url += 'theme=' + this.theme; - url += '&mode=' + this.mode; - if (this.isRTL) { + url += 'theme=' + this.theme(); + url += '&mode=' + this.mode(); + if (this.isRTL()) { url += '&isRTL=true'; } - if (this.locale) { - url += '&locale=' + this.locale; + const locale = this.locale(); + if (locale) { + url += '&locale=' + locale; } - if (this.rootFontSize) { - url += '&rfs=' + this.rootFontSize; + if (this.rootFontSize()) { + url += '&rfs=' + this.rootFontSize(); } if (this.templateModified && !skipTemplate) { - url += '&t=' + this.encode(this.template); + url += '&t=' + this.encode(this.template()); } - if (this.exampleUrl) { - url += '&e=' + this.encode(this.exampleUrl); + const exampleUrl = this.exampleUrl(); + if (exampleUrl) { + url += '&e=' + this.encode(exampleUrl); } return url; } @@ -235,32 +247,34 @@ export class SiLivePreviewIframeComponent implements OnInit, OnChanges { } private sendMessage(): void { + const landscape = this.landscape(); + const selectedDevice = this.selectedDevice(); this.previewIframe()?.nativeElement.contentWindow.postMessage( { src: 'editor', - exampleUrl: this.baseUrl + this.exampleUrl, - template: this.template, - loadReact: this.loadReact, - loadVue: this.loadVue, - loadJs: this.loadJs, - reactVueTemplate: this.reactVueTemplate, - theme: this.theme, - locale: this.locale, - rootFontSize: this.rootFontSize, - isRTL: this.isRTL, - mode: this.mode, - safeAreaTop: this.landscape - ? this.selectedDevice?.safeAreaLandscape?.top - : this.selectedDevice?.safeAreaPortrait?.top, - safeAreaBottom: this.landscape - ? this.selectedDevice?.safeAreaLandscape?.bottom - : this.selectedDevice?.safeAreaPortrait?.bottom, - safeAreaLeft: this.landscape - ? this.selectedDevice?.safeAreaLandscape?.left - : this.selectedDevice?.safeAreaPortrait?.left, - safeAreaRight: this.landscape - ? this.selectedDevice?.safeAreaLandscape?.right - : this.selectedDevice?.safeAreaPortrait?.right + exampleUrl: this.baseUrl() + this.exampleUrl(), + template: this.template(), + loadReact: this.loadReact(), + loadVue: this.loadVue(), + loadJs: this.loadJs(), + reactVueTemplate: this.reactVueTemplate(), + theme: this.theme(), + locale: this.locale(), + rootFontSize: this.rootFontSize(), + isRTL: this.isRTL(), + mode: this.mode(), + safeAreaTop: landscape + ? selectedDevice?.safeAreaLandscape?.top + : selectedDevice?.safeAreaPortrait?.top, + safeAreaBottom: landscape + ? selectedDevice?.safeAreaLandscape?.bottom + : selectedDevice?.safeAreaPortrait?.bottom, + safeAreaLeft: landscape + ? selectedDevice?.safeAreaLandscape?.left + : selectedDevice?.safeAreaPortrait?.left, + safeAreaRight: landscape + ? selectedDevice?.safeAreaLandscape?.right + : selectedDevice?.safeAreaPortrait?.right }, '*' ); diff --git a/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.html b/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.html index 6b4defbd80..13eaa88c27 100644 --- a/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.html +++ b/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.html @@ -1,9 +1,9 @@ -
-@if (qrShort) { +
+@if (qrShort()) { Note: not using template, too long for QR - ({{ url?.length }}) + ({{ url()?.length }}) }
-Open in new tab +Open in new tab diff --git a/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.ts b/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.ts index 216ce0c739..c51aa23fd3 100644 --- a/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.ts +++ b/projects/live-preview/components/si-live-preview-qr/si-live-preview-qr.component.ts @@ -6,11 +6,10 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, - Input, - OnChanges, + computed, + input, OnDestroy, - output, - SimpleChanges + output } from '@angular/core'; import qrcode from 'qrcode-generator'; @@ -20,21 +19,26 @@ import qrcode from 'qrcode-generator'; styleUrl: './si-live-preview-qr.component.scss', changeDetection: ChangeDetectionStrategy.Eager }) -export class SiLivePreviewQrComponent implements AfterViewInit, OnDestroy, OnChanges { - @Input() url?: string; - @Input() urlShort?: string; +export class SiLivePreviewQrComponent implements AfterViewInit, OnDestroy { + readonly url = input(); + readonly urlShort = input(); readonly closed = output(); - qrImg = ''; - qrShort = false; + readonly qrImg = computed(() => { + const url = this.url(); + const urlShort = this.urlShort(); + if (!url || !urlShort) { + return null; + } + const qr = qrcode(0, 'L'); + qr.addData(this.qrShort() ? urlShort : url); + qr.make(); + return qr.createImgTag(2); + }); - private unlisten?: () => void; + readonly qrShort = computed(() => (this.url()?.length ?? 0) > 2953); - ngOnChanges(changes: SimpleChanges): void { - if (changes.url && changes.urlShort) { - this.generateQr(); - } - } + private unlisten?: () => void; ngAfterViewInit(): void { const listener = (): void => this.closed.emit(); @@ -47,13 +51,4 @@ export class SiLivePreviewQrComponent implements AfterViewInit, OnDestroy, OnCha this.unlisten(); } } - - private generateQr(): void { - this.qrShort = (this.url?.length ?? 0) > 2953; - const url = this.qrShort ? this.urlShort! : this.url!; - const qr = qrcode(0, 'L'); - qr.addData(url); - qr.make(); - this.qrImg = qr.createImgTag(2); - } } diff --git a/projects/live-preview/components/si-live-preview-renderer/si-live-preview-renderer.component.ts b/projects/live-preview/components/si-live-preview-renderer/si-live-preview-renderer.component.ts index 1b3e65e253..ac270576bd 100644 --- a/projects/live-preview/components/si-live-preview-renderer/si-live-preview-renderer.component.ts +++ b/projects/live-preview/components/si-live-preview-renderer/si-live-preview-renderer.component.ts @@ -12,23 +12,24 @@ import { ComponentRef, createNgModule, DoCheck, + effect, ElementRef, EnvironmentInjector, - HostBinding, inject, + input, Injector, - Input, NgModule, NgModuleRef, - OnChanges, OnDestroy, + output, ɵresetCompiledComponents as resetCompiledComponents, - SimpleChanges, + signal, + untracked, ViewContainerRef, viewChild, ViewChild, - output, - ChangeDetectionStrategy + ChangeDetectionStrategy, + afterNextRender } from '@angular/core'; import { ɵDomRendererFactory2 as DomRendererFactory2 } from '@angular/platform-browser'; import { ActivatedRoute, Routes } from '@angular/router'; @@ -66,23 +67,22 @@ export class DummyAppSampleComponent {} @Component({ selector: 'si-live-preview-renderer', template: '
', - changeDetection: ChangeDetectionStrategy.Eager + changeDetection: ChangeDetectionStrategy.Eager, + host: { + '[class.live-preview-done]': 'renderingDone()', + '[attr.data-example]': 'exampleUrl()', + '[attr.data-id]': 'dataId()' + } }) -export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { +export class SiLivePreviewRendererComponent implements OnDestroy { readonly renderedExample = viewChild.required('renderedExample', { read: ViewContainerRef }); readonly react = viewChild.required('react', { read: ElementRef }); - @HostBinding('class.live-preview-done') renderingDone = false; - - @HostBinding('attr.data-example') - @Input() - exampleUrl!: string; - - @HostBinding('attr.data-id') - @Input() - dataId = ''; + readonly exampleUrl = input.required(); + readonly dataId = input(''); + readonly template = input(''); - @Input() template = ''; + protected readonly renderingDone = signal(false); readonly templateFromComponent = output(); readonly logClear = output(); @@ -106,8 +106,8 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { private compiledTemplate?: string; // eslint-disable-next-line @typescript-eslint/no-deprecated - private compiler = inject(Compiler); - private injector = Injector.create({ + private readonly compiler = inject(Compiler); + private readonly injector = Injector.create({ parent: inject(Injector), providers: [ { @@ -116,27 +116,34 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { } ] }); - private envInjector = inject(EnvironmentInjector); - private rendererFactory = inject(DomRendererFactory2); - private config = inject(SI_LIVE_PREVIEW_CONFIG); - private internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); - private activatedRoute = inject(ActivatedRoute); - private defaultRoutes = this.activatedRoute.routeConfig?.children ?? []; - private cdRef = inject(ChangeDetectorRef); - - ngOnChanges(changes: SimpleChanges): void { - if (changes.exampleUrl?.currentValue) { - this.loadFromUrl(); - } - if (changes.template?.currentValue) { + private readonly envInjector = inject(EnvironmentInjector); + private readonly rendererFactory = inject(DomRendererFactory2); + private readonly config = inject(SI_LIVE_PREVIEW_CONFIG); + private readonly internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); + private readonly activatedRoute = inject(ActivatedRoute); + private readonly defaultRoutes = this.activatedRoute.routeConfig?.children ?? []; + private readonly cdRef = inject(ChangeDetectorRef); + + constructor() { + effect(() => { + const url = this.exampleUrl(); + if (url) { + untracked(() => this.loadFromUrl()); + } + }); + + effect(() => { + const template = this.template(); // when the initial template is pushed from the renderer to the editor, this.template is // initially empty, but later changes to what the renderer has pushed out. In this case // don't compile - if (this.template !== this.compiledTemplate) { - this.setInProgress(true); - this.compileWhenReady(); + if (template && template !== this.compiledTemplate) { + untracked(() => { + this.setInProgress(true); + this.compileWhenReady(); + }); } - } + }); } ngOnDestroy(): void { @@ -180,7 +187,7 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { this.dynamicComponent = undefined; this.componentTsFirstLoad = true; - this.componentTs = this.config.componentLoader.load(this.exampleUrl); + this.componentTs = this.config.componentLoader.load(this.exampleUrl()); if (this.componentTs) { this.setInProgress(true); this.componentTs @@ -189,20 +196,26 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { this.dynamicComponent = m.SampleComponent; this.componentTsSampleModule = m.SampleModule; this.compileWhenReady(); - if (!this.componentTsSampleComponent && !this.template) { - setTimeout(() => { - this.setInProgress(false); - this.cdRef.markForCheck(); - }); + if (!this.componentTsSampleComponent && !this.template()) { + afterNextRender( + () => { + this.setInProgress(false); + this.cdRef.markForCheck(); + }, + { injector: this.injector } + ); } }) .catch(e => { this.componentTs = undefined; this.logRenderingError.emit(e ? e.toString() : 'Failed loading TS'); - setTimeout(() => { - this.setInProgress(false); - this.cdRef.markForCheck(); - }); + afterNextRender( + () => { + this.setInProgress(false); + this.cdRef.markForCheck(); + }, + { injector: this.injector } + ); }); } else { // set the dummy component @@ -214,7 +227,7 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { } private setInProgress(inProgress: boolean): void { - this.renderingDone = !inProgress; + this.renderingDone.set(!inProgress); this.inProgress.emit(inProgress); } @@ -224,11 +237,11 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { // TS, but code not yet loaded return; } - if (!this.componentTsFirstLoad && !this.template) { + if (!this.componentTsFirstLoad && !this.template()) { // TS, first load done, template not yet available return; } - } else if (!this.template) { + } else if (!this.template()) { // no TS, no template return; } @@ -277,16 +290,16 @@ export class SiLivePreviewRendererComponent implements OnChanges, OnDestroy { const supportsLandscape = ann.providers?.includes(LandscapeSupportService); this.supportsLandscapeMode.emit(supportsLandscape); - if (this.componentTsFirstLoad && !this.template) { + if (this.componentTsFirstLoad && !this.template()) { // skipping compile on first load this.compiledTemplate = ann.template; this.templateFromComponent.emit(ann.template); - } else if (ann.template !== this.template) { + } else if (ann.template !== this.template()) { // recompile with new template this.componentTsFirstLoad = false; this.cloneComponentWithoutIvyStuff(); - ann.template = this.template; - this.compiledTemplate = this.template; + ann.template = this.template(); + this.compiledTemplate = this.template(); compileComponent(this.dynamicComponent, ann); this.reuseResolvedScopeOfOriginalComponent(); } diff --git a/projects/live-preview/components/si-live-preview-renderer/webcomponent/si-live-preview-web.component.ts b/projects/live-preview/components/si-live-preview-renderer/webcomponent/si-live-preview-web.component.ts index 6ee99b449c..032f05b7dc 100644 --- a/projects/live-preview/components/si-live-preview-renderer/webcomponent/si-live-preview-web.component.ts +++ b/projects/live-preview/components/si-live-preview-renderer/webcomponent/si-live-preview-web.component.ts @@ -6,10 +6,10 @@ import { ChangeDetectionStrategy, Component, ElementRef, - HostBinding, - Input, + input, OnChanges, output, + signal, SimpleChanges, viewChild } from '@angular/core'; @@ -19,19 +19,22 @@ import { SiLivePreviewConfig } from '../../../interfaces/live-preview-config'; @Component({ selector: 'si-live-preview-webcomponent', template: '
', - changeDetection: ChangeDetectionStrategy.Eager + changeDetection: ChangeDetectionStrategy.Eager, + host: { + '[class.live-preview-done]': 'renderingDone()' + } }) export class SiLivePreviewWebComponent implements OnChanges { readonly root = viewChild.required('root', { read: ElementRef }); - @Input() loadReact = false; - @Input() loadVue = false; - @Input() loadJs = false; - @Input() webcomponentTemplateCode = ''; - @Input() exampleUrl!: string; - @Input() config!: SiLivePreviewConfig; + readonly loadReact = input(false); + readonly loadVue = input(false); + readonly loadJs = input(false); + readonly webcomponentTemplateCode = input(''); + readonly exampleUrl = input(''); + readonly config = input(); readonly inProgress = output(); - @HostBinding('class.live-preview-done') renderingDone = false; + readonly renderingDone = signal(false); private reactRoot: any; private vueRoot: any; @@ -43,19 +46,19 @@ export class SiLivePreviewWebComponent implements OnChanges { changes.loadReact?.currentValue && changes.loadReact?.currentValue !== changes.loadReact?.previousValue ) { - this.loadReactFromCodeTemplate(this.webcomponentTemplateCode); + this.loadReactFromCodeTemplate(this.webcomponentTemplateCode()); } if ( changes.loadVue?.currentValue && changes.loadVue?.currentValue !== changes.loadVue?.previousValue ) { - this.loadVueFromCodeTemplate(this.webcomponentTemplateCode); + this.loadVueFromCodeTemplate(this.webcomponentTemplateCode()); } if ( changes.loadJs?.currentValue && changes.loadJs?.currentValue !== changes.loadJs?.previousValue ) { - this.loadHTMLFromCodeTemplate(this.webcomponentTemplateCode); + this.loadHTMLFromCodeTemplate(this.webcomponentTemplateCode()); } } @@ -135,7 +138,7 @@ export class SiLivePreviewWebComponent implements OnChanges { } private setInProgress(inProgress: boolean): void { - this.renderingDone = !inProgress; + this.renderingDone.set(!inProgress); this.inProgress.emit(inProgress); } diff --git a/projects/live-preview/components/si-live-preview-wrapper/si-live-preview-wrapper.component.html b/projects/live-preview/components/si-live-preview-wrapper/si-live-preview-wrapper.component.html index 09a99c0fa1..bca83c4a46 100644 --- a/projects/live-preview/components/si-live-preview-wrapper/si-live-preview-wrapper.component.html +++ b/projects/live-preview/components/si-live-preview-wrapper/si-live-preview-wrapper.component.html @@ -1,8 +1,8 @@ ('renderer'); readonly webcomponentRenderer = viewChild.required('webcomponentRenderer'); - exampleUrl!: string; - template = ''; - loadReact = false; - loadVue = false; - loadJs = false; - webcomponentTemplateCode = ''; + readonly exampleUrl = signal(''); + readonly template = signal(''); + readonly loadReact = signal(false); + readonly loadVue = signal(false); + readonly loadJs = signal(false); + readonly webcomponentTemplateCode = signal(''); private isMobile = false; private theme!: ThemeType; private isRTL = false; @@ -55,13 +58,15 @@ export class SiLivePreviewWrapperComponent { private rootFontSize?: number | 'initial'; private initialUrl: string; - private config = inject(SI_LIVE_PREVIEW_CONFIG); - private themeApi = inject(SiLivePreviewThemeApi, { optional: true }); - private localeApi = inject(SiLivePreviewLocaleApi, { optional: true }); - private internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); - private ngZone = inject(NgZone); - private webcomponentService = inject(SiLivePreviewWebComponentService, { optional: true }); - private cdRef = inject(ChangeDetectorRef); + private readonly config = inject(SI_LIVE_PREVIEW_CONFIG); + private readonly themeApi = inject(SiLivePreviewThemeApi, { optional: true }); + private readonly localeApi = inject(SiLivePreviewLocaleApi, { optional: true }); + private readonly internalConfig = inject(SI_LIVE_PREVIEW_INTERNALS); + private readonly ngZone = inject(NgZone); + private readonly webcomponentService = inject(SiLivePreviewWebComponentService, { + optional: true + }); + private readonly cdRef = inject(ChangeDetectorRef); constructor() { this.themeApi @@ -105,12 +110,12 @@ export class SiLivePreviewWrapperComponent { } private onMessageInZone(event: MessageEvent): void { - this.exampleUrl = event.data.exampleUrl; - this.template = event.data.template; - this.loadReact = event.data.loadReact; - this.loadVue = event.data.loadVue; - this.loadJs = event.data.loadJs; - this.webcomponentTemplateCode = event.data.reactVueTemplate; + this.exampleUrl.set(event.data.exampleUrl); + this.template.set(event.data.template); + this.loadReact.set(event.data.loadReact); + this.loadVue.set(event.data.loadVue); + this.loadJs.set(event.data.loadJs); + this.webcomponentTemplateCode.set(event.data.reactVueTemplate); if (this.theme !== event.data.theme) { this.setTheme(event.data.theme); @@ -144,15 +149,15 @@ export class SiLivePreviewWrapperComponent { } const webcomponentRenderer = this.webcomponentRenderer(); - if (webcomponentRenderer && (this.loadReact || this.loadVue || this.loadJs)) { + if (webcomponentRenderer && (this.loadReact() || this.loadVue() || this.loadJs())) { this.webcomponentService?.injectComponent( webcomponentRenderer, { - exampleUrl: this.exampleUrl, - loadReact: this.loadReact, - loadJs: this.loadJs, - webcomponentTemplateCode: this.webcomponentTemplateCode, - loadVue: this.loadVue, + exampleUrl: this.exampleUrl(), + loadReact: this.loadReact(), + loadJs: this.loadJs(), + webcomponentTemplateCode: this.webcomponentTemplateCode(), + loadVue: this.loadVue(), config: this.config }, { @@ -164,7 +169,7 @@ export class SiLivePreviewWrapperComponent { } } - @HostListener('click', ['$event']) onClick(event: MouseEvent): void { + onClick(event: MouseEvent): void { const target = event?.target as HTMLElement; if (target?.tagName === 'A' && !event.defaultPrevented) { // for normal link: ok if it starts with the proper route, else open in a new window diff --git a/projects/live-preview/components/si-live-preview/si-live-preview.component.html b/projects/live-preview/components/si-live-preview/si-live-preview.component.html index 5d2ef2de94..fd6c69db1e 100644 --- a/projects/live-preview/components/si-live-preview/si-live-preview.component.html +++ b/projects/live-preview/components/si-live-preview/si-live-preview.component.html @@ -1,21 +1,22 @@ +@let active = activeTab();
} - @if (isFullscreen) { + @if (isFullscreen()) {
- @if (allowFullscreen && !isFullscreen) { + @if (allowFullscreen && !isFullscreen()) {
@if (webcomponents && showEditor && frameworks.size > 1) {