@kustomizer/visual-editor 0.1.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,7 +1,7 @@
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
- import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
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';
6
6
  import { HttpClient } from '@angular/common/http';
7
7
  import { createActionGroup, emptyProps, props, createReducer, on, createFeatureSelector, createSelector, provideState, Store } from '@ngrx/store';
@@ -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);
@@ -3260,6 +3294,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
3260
3294
  type: Injectable
3261
3295
  }], ctorParameters: () => [] });
3262
3296
 
3297
+ /**
3298
+ * Service that holds and dynamically loads the per-merchant storefront URL.
3299
+ *
3300
+ * Resolution order:
3301
+ * 1. Metafield value loaded from GET /api/storefront-url (per-shop)
3302
+ * 2. Static STOREFRONT_URL injection token (env var / window global fallback)
3303
+ * 3. Empty string (editor works with local components only)
3304
+ */
3305
+ class StorefrontUrlService {
3306
+ http = inject(HttpClient);
3307
+ staticUrl = inject(STOREFRONT_URL);
3308
+ /** Current storefront URL — reactive signal used by the editor and iframe. */
3309
+ url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
3310
+ /**
3311
+ * Load the storefront URL from the backend metafield.
3312
+ * Falls back to the static STOREFRONT_URL token if the endpoint
3313
+ * returns no value or fails.
3314
+ *
3315
+ * Called during APP_INITIALIZER.
3316
+ */
3317
+ async load() {
3318
+ try {
3319
+ const res = await firstValueFrom(this.http.get('/api/storefront-url'));
3320
+ if (res.url) {
3321
+ this.url.set(res.url);
3322
+ return;
3323
+ }
3324
+ }
3325
+ catch {
3326
+ // Non-fatal — fall back to static token
3327
+ }
3328
+ // Fallback: use the static injection token (env var / window global)
3329
+ if (this.staticUrl) {
3330
+ this.url.set(this.staticUrl);
3331
+ }
3332
+ }
3333
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3334
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, providedIn: 'root' });
3335
+ }
3336
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, decorators: [{
3337
+ type: Injectable,
3338
+ args: [{ providedIn: 'root' }]
3339
+ }] });
3340
+
3263
3341
  const MAX_DEPTH = 12;
3264
3342
  class DragDropService {
3265
3343
  store = inject(Store);
@@ -4391,7 +4469,7 @@ class VisualEditorComponent {
4391
4469
  config = inject(VISUAL_EDITOR_CONFIG);
4392
4470
  dndService = inject(DragDropService);
4393
4471
  iframeBridge = inject(IframeBridgeService);
4394
- storefrontUrl = inject(STOREFRONT_URL);
4472
+ storefrontUrlService = inject(StorefrontUrlService);
4395
4473
  sanitizer = inject(DomSanitizer);
4396
4474
  destroy$ = new Subject();
4397
4475
  // Configuration-driven UI options
@@ -4404,9 +4482,10 @@ class VisualEditorComponent {
4404
4482
  // Iframe preview
4405
4483
  previewFrame = viewChild('previewFrame', ...(ngDevMode ? [{ debugName: "previewFrame" }] : []));
4406
4484
  previewUrl = computed(() => {
4407
- if (!this.storefrontUrl)
4485
+ const url = this.storefrontUrlService.url();
4486
+ if (!url)
4408
4487
  return null;
4409
- return this.sanitizer.bypassSecurityTrustResourceUrl(`${this.storefrontUrl}/kustomizer/editor`);
4488
+ return this.sanitizer.bypassSecurityTrustResourceUrl(`${url}/kustomizer/editor`);
4410
4489
  }, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
4411
4490
  iframeReady = false;
4412
4491
  propertiesTab = signal('props', ...(ngDevMode ? [{ debugName: "propertiesTab" }] : []));
@@ -4673,7 +4752,8 @@ class VisualEditorComponent {
4673
4752
  onIframeLoad() {
4674
4753
  const iframe = this.previewFrame()?.nativeElement;
4675
4754
  if (iframe) {
4676
- const origin = this.storefrontUrl ? new URL(this.storefrontUrl).origin : '*';
4755
+ const sfUrl = this.storefrontUrlService.url();
4756
+ const origin = sfUrl ? new URL(sfUrl).origin : '*';
4677
4757
  this.iframeBridge.connect(iframe, origin);
4678
4758
  }
4679
4759
  }
@@ -7768,22 +7848,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
7768
7848
  class ManifestLoaderService {
7769
7849
  http = inject(HttpClient);
7770
7850
  registry = inject(ComponentRegistryService);
7851
+ watchInterval = null;
7852
+ lastManifestHash = '';
7853
+ manifestTypes = new Set();
7771
7854
  /**
7772
7855
  * Fetch the manifest from the given URL and register all components.
7773
7856
  */
7774
7857
  loadManifest(url) {
7775
- 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
+ }
7776
7889
  }
7777
7890
  /**
7778
7891
  * Register manifest entries in the ComponentRegistryService.
7779
7892
  * Each entry becomes a ComponentDefinition without a `component` field.
7780
7893
  */
7781
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();
7782
7900
  for (const entry of manifest.components) {
7783
7901
  const definition = this.manifestEntryToDefinition(entry);
7784
7902
  this.registry.register(definition);
7903
+ this.manifestTypes.add(definition.type);
7785
7904
  }
7786
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
+ }
7787
7909
  manifestEntryToDefinition(entry) {
7788
7910
  return {
7789
7911
  type: entry.type,
@@ -7839,5 +7961,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
7839
7961
  * Generated bundle index. Do not edit.
7840
7962
  */
7841
7963
 
7842
- export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
7964
+ export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, StorefrontUrlService, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
7843
7965
  //# sourceMappingURL=kustomizer-visual-editor.mjs.map