@kustomizer/visual-editor 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, makeEnvironmentProviders, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, computed, NgZone, signal, viewChild, output } from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, makeEnvironmentProviders, provideEnvironmentInitializer, signal, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, computed, NgZone, viewChild, output, isDevMode } from '@angular/core';
3
3
  import { Router, ActivatedRoute } from '@angular/router';
4
4
  import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, firstValueFrom, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
5
5
  import { switchMap, map as map$1, catchError } from 'rxjs/operators';
@@ -2104,6 +2104,17 @@ function provideEditorComponents(definitions) {
2104
2104
  useValue: definitions,
2105
2105
  multi: true,
2106
2106
  },
2107
+ // Fallback: directly register definitions via environment initializer.
2108
+ // This ensures registration works even when the InjectionToken doesn't
2109
+ // match between pre-bundled library code and app code (e.g. ng serve with Vite).
2110
+ provideEnvironmentInitializer(() => {
2111
+ const registry = inject(ComponentRegistryService);
2112
+ for (const def of definitions) {
2113
+ if (!registry.has(def.type)) {
2114
+ registry.register(def);
2115
+ }
2116
+ }
2117
+ }),
2107
2118
  ]);
2108
2119
  }
2109
2120
 
@@ -2112,10 +2123,28 @@ function provideEditorComponents(definitions) {
2112
2123
  */
2113
2124
  class ComponentRegistryService {
2114
2125
  definitions = new Map();
2126
+ _version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : []));
2115
2127
  injectedDefinitions = inject(EDITOR_COMPONENT_DEFINITIONS, { optional: true }) ?? [];
2116
2128
  constructor() {
2117
2129
  this.injectedDefinitions.flat().forEach((def) => this.register(def));
2118
2130
  }
2131
+ /**
2132
+ * Clear all registered definitions
2133
+ */
2134
+ clear() {
2135
+ this.definitions.clear();
2136
+ this._version.update(v => v + 1);
2137
+ }
2138
+ /**
2139
+ * Unregister a single component definition by type
2140
+ */
2141
+ unregister(type) {
2142
+ const deleted = this.definitions.delete(type);
2143
+ if (deleted) {
2144
+ this._version.update(v => v + 1);
2145
+ }
2146
+ return deleted;
2147
+ }
2119
2148
  /**
2120
2149
  * Register a component definition
2121
2150
  */
@@ -2124,6 +2153,7 @@ class ComponentRegistryService {
2124
2153
  console.warn(`ComponentRegistry: Component type "${definition.type}" is already registered. Overwriting.`);
2125
2154
  }
2126
2155
  this.definitions.set(definition.type, definition);
2156
+ this._version.update(v => v + 1);
2127
2157
  }
2128
2158
  /**
2129
2159
  * Register multiple definitions
@@ -2172,6 +2202,7 @@ class ComponentRegistryService {
2172
2202
  * Get all definitions
2173
2203
  */
2174
2204
  getAll() {
2205
+ this._version(); // reactive dependency for signal consumers
2175
2206
  return Array.from(this.definitions.values());
2176
2207
  }
2177
2208
  /**
@@ -2365,8 +2396,7 @@ class DynamicRendererComponent {
2365
2396
  /** Additional context (sectionId, index, etc.) */
2366
2397
  context = input({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
2367
2398
  /** Error state signal */
2368
- _error = false;
2369
- error = () => this._error;
2399
+ error = signal(false, ...(ngDevMode ? [{ debugName: "error" }] : []));
2370
2400
  componentRef = null;
2371
2401
  currentType = null;
2372
2402
  currentAvailableInputs = null;
@@ -2392,13 +2422,17 @@ class DynamicRendererComponent {
2392
2422
  this.cleanup();
2393
2423
  const componentType = this.registry.getComponent(element.type);
2394
2424
  if (!componentType) {
2395
- this._error = true;
2425
+ this.error.set(true);
2396
2426
  this.currentType = null;
2397
2427
  this.currentAvailableInputs = null;
2398
- console.warn(`DynamicRenderer: No component registered for type "${element.type}"`);
2428
+ const all = this.registry.getAll();
2429
+ const hasType = this.registry.has(element.type);
2430
+ console.warn(`DynamicRenderer: No component registered for type "${element.type}".`, hasType
2431
+ ? `Definition exists but has no "component" class (manifest-only).`
2432
+ : `Registry has ${all.length} definition(s): [${all.map(d => d.type).join(', ')}]`);
2399
2433
  return;
2400
2434
  }
2401
- this._error = false;
2435
+ this.error.set(false);
2402
2436
  this.currentType = element.type;
2403
2437
  this.previousInputs.clear();
2404
2438
  this.componentRef = this.viewContainerRef.createComponent(componentType);
@@ -7814,22 +7848,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
7814
7848
  class ManifestLoaderService {
7815
7849
  http = inject(HttpClient);
7816
7850
  registry = inject(ComponentRegistryService);
7851
+ watchInterval = null;
7852
+ lastManifestHash = '';
7853
+ manifestTypes = new Set();
7817
7854
  /**
7818
7855
  * Fetch the manifest from the given URL and register all components.
7819
7856
  */
7820
7857
  loadManifest(url) {
7821
- return this.http.get(url).pipe(tap((manifest) => this.registerManifestComponents(manifest)));
7858
+ return this.http.get(url).pipe(tap((manifest) => {
7859
+ this.registerManifestComponents(manifest);
7860
+ this.lastManifestHash = this.hashManifest(manifest);
7861
+ }));
7862
+ }
7863
+ /**
7864
+ * Poll the manifest URL for changes and re-register components when the
7865
+ * content hash differs. Only active in dev mode. No-op in production.
7866
+ */
7867
+ startWatching(manifestUrl, intervalMs = 2000) {
7868
+ if (!isDevMode() || this.watchInterval)
7869
+ return;
7870
+ this.watchInterval = setInterval(() => {
7871
+ this.http.get(manifestUrl).subscribe({
7872
+ next: (manifest) => {
7873
+ const hash = this.hashManifest(manifest);
7874
+ if (hash !== this.lastManifestHash) {
7875
+ console.log('[Kustomizer] Manifest changed, reloading components…');
7876
+ this.lastManifestHash = hash;
7877
+ this.registerManifestComponents(manifest);
7878
+ }
7879
+ },
7880
+ error: () => { }, // silently ignore polling errors
7881
+ });
7882
+ }, intervalMs);
7883
+ }
7884
+ ngOnDestroy() {
7885
+ if (this.watchInterval) {
7886
+ clearInterval(this.watchInterval);
7887
+ this.watchInterval = null;
7888
+ }
7822
7889
  }
7823
7890
  /**
7824
7891
  * Register manifest entries in the ComponentRegistryService.
7825
7892
  * Each entry becomes a ComponentDefinition without a `component` field.
7826
7893
  */
7827
7894
  registerManifestComponents(manifest) {
7895
+ // Remove only previously manifest-loaded definitions, preserving local components
7896
+ for (const type of this.manifestTypes) {
7897
+ this.registry.unregister(type);
7898
+ }
7899
+ this.manifestTypes.clear();
7828
7900
  for (const entry of manifest.components) {
7829
7901
  const definition = this.manifestEntryToDefinition(entry);
7830
7902
  this.registry.register(definition);
7903
+ this.manifestTypes.add(definition.type);
7831
7904
  }
7832
7905
  }
7906
+ hashManifest(manifest) {
7907
+ return JSON.stringify(manifest.components.map(c => c.type + c.name + JSON.stringify(c.props ?? {}) + JSON.stringify(c.slots ?? [])));
7908
+ }
7833
7909
  manifestEntryToDefinition(entry) {
7834
7910
  return {
7835
7911
  type: entry.type,