@vectoriox/iox-builder 1.4.93 → 1.4.95

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.
@@ -5440,10 +5440,39 @@ class SliderContainerComponent {
5440
5440
  // the nav buttons (position:absolute) are anchored to THIS element, not a canvas ancestor.
5441
5441
  return { ...this.style, position: 'relative' };
5442
5442
  }
5443
- constructor(dragEngine, cdr, isPreview) {
5443
+ get source() { return this._source; }
5444
+ set source(value) {
5445
+ if (value === this._source)
5446
+ return;
5447
+ this._source = value;
5448
+ if (value) {
5449
+ this.fetchData();
5450
+ }
5451
+ else {
5452
+ this.items = [];
5453
+ this.previewSlides = [];
5454
+ this._scheduleReinit();
5455
+ }
5456
+ }
5457
+ get limit() { return this._limit; }
5458
+ set limit(value) {
5459
+ if (value === this._limit)
5460
+ return;
5461
+ this._limit = value;
5462
+ if (this._source) {
5463
+ this.fetchData();
5464
+ }
5465
+ }
5466
+ constructor(dragEngine, cdr, isPreview, overlayService, contentService, shopContentService, dsRegistry, panelEventService, activatedRoute) {
5444
5467
  this.dragEngine = dragEngine;
5445
5468
  this.cdr = cdr;
5446
5469
  this.isPreview = isPreview;
5470
+ this.overlayService = overlayService;
5471
+ this.contentService = contentService;
5472
+ this.shopContentService = shopContentService;
5473
+ this.dsRegistry = dsRegistry;
5474
+ this.panelEventService = panelEventService;
5475
+ this.activatedRoute = activatedRoute;
5447
5476
  this.children = [];
5448
5477
  this.style = {};
5449
5478
  this.nodeId = '';
@@ -5490,16 +5519,37 @@ class SliderContainerComponent {
5490
5519
  this.gridRows = 2;
5491
5520
  // ── Responsive ─────────────────────────────────────────────────────────────
5492
5521
  this.breakpoints = '';
5522
+ // ── Data binding ───────────────────────────────────────────────────────────
5523
+ this._source = '';
5524
+ this._limit = 10;
5525
+ this.items = [];
5526
+ this.previewSlides = [];
5527
+ this.isLoading = false;
5493
5528
  this.childClick = new EventEmitter();
5494
5529
  this.childMouseEnter = new EventEmitter();
5495
5530
  this.childMouseLeave = new EventEmitter();
5496
5531
  this._swiper = null;
5497
5532
  this._initRafId = 0;
5533
+ this._orgId = 'default';
5498
5534
  this.instanceRegistry = inject(ComponentInstanceRegistryService);
5499
5535
  this.interactionEngine = inject(InteractionEngineService);
5500
5536
  this.isPreview = !!isPreview;
5501
5537
  }
5502
- ngOnInit() { }
5538
+ ngOnInit() {
5539
+ this._orgId = this.activatedRoute?.snapshot.paramMap.get('orgId') || 'default';
5540
+ if (this.panelEventService) {
5541
+ this._bindingSub = this.panelEventService.subscribe(event => {
5542
+ if (event.type !== PanelEventTypes.BINDING_CHANGED)
5543
+ return;
5544
+ const changedNode = event.data?.node;
5545
+ if (!changedNode || !this.items.length || !this._source)
5546
+ return;
5547
+ if (this._isInTemplate(changedNode)) {
5548
+ this.refreshPreviews();
5549
+ }
5550
+ });
5551
+ }
5552
+ }
5503
5553
  ngAfterViewInit() {
5504
5554
  this._initRafId = requestAnimationFrame(() => this._initSwiper());
5505
5555
  }
@@ -5517,7 +5567,7 @@ class SliderContainerComponent {
5517
5567
  'autoplayDelay', 'autoplayPauseOnMouseEnter', 'autoplayDisableOnInteraction',
5518
5568
  'gridRows',
5519
5569
  ]);
5520
- // Edit mode also reinits when children change (new slide added)
5570
+ // Edit mode also reinits when children change (new slide added or removed)
5521
5571
  const editKeys = new Set([...previewKeys, 'children']);
5522
5572
  const relevantKeys = this.isPreview ? previewKeys : editKeys;
5523
5573
  const hasConfigChange = Object.keys(changes).some(k => relevantKeys.has(k));
@@ -5530,14 +5580,96 @@ class SliderContainerComponent {
5530
5580
  ngOnDestroy() {
5531
5581
  cancelAnimationFrame(this._initRafId);
5532
5582
  this._destroySwiper();
5583
+ this._bindingSub?.unsubscribe();
5584
+ }
5585
+ trackByNodeId(_index, node) {
5586
+ return node.id ?? String(_index);
5533
5587
  }
5534
5588
  onDrop(event) {
5589
+ if (this._source)
5590
+ return; // repeat mode: drops disabled, slides come from data source
5535
5591
  this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
5536
5592
  // Sync Swiper's internal slide list after drag-drop reorder
5537
5593
  if (this._swiper) {
5538
5594
  requestAnimationFrame(() => this._swiper?.update());
5539
5595
  }
5540
5596
  }
5597
+ // ── Data fetching ───────────────────────────────────────────────────────────
5598
+ fetchData() {
5599
+ if (!this._source || !this.dsRegistry)
5600
+ return;
5601
+ const ds = this.dsRegistry.findByAlias(this._source);
5602
+ const isShop = ds?.type === DataSourceType.Shop;
5603
+ const contentType = isShop ? ds?.request?.shop?.contentType : ds?.request?.cms?.contentType;
5604
+ const service = isShop ? this.shopContentService : this.contentService;
5605
+ if (!contentType || !service)
5606
+ return;
5607
+ this.isLoading = true;
5608
+ this.cdr.markForCheck();
5609
+ service.getContent(contentType, this._orgId).subscribe({
5610
+ next: (data) => {
5611
+ const all = Array.isArray(data) ? data : (data?.items || data?.data || []);
5612
+ this.items = all.slice(0, this._limit);
5613
+ this.refreshPreviews();
5614
+ this.isLoading = false;
5615
+ this.cdr.markForCheck();
5616
+ },
5617
+ error: () => {
5618
+ this.isLoading = false;
5619
+ this.cdr.markForCheck();
5620
+ },
5621
+ });
5622
+ }
5623
+ refreshPreviews() {
5624
+ if (!this.items.length || !this.children.length) {
5625
+ this.previewSlides = [];
5626
+ this.cdr.markForCheck();
5627
+ this._scheduleReinit();
5628
+ return;
5629
+ }
5630
+ this.applyFirstItemToTemplate();
5631
+ // Each preview slide = one cloned SliderSlide node with item data resolved
5632
+ this.previewSlides = this.items.slice(1).map(item => cloneNodesForItem([this.children[0]], item)[0]);
5633
+ this.cdr.markForCheck();
5634
+ this._scheduleReinit();
5635
+ }
5636
+ applyFirstItemToTemplate() {
5637
+ const item = this.items[0];
5638
+ if (!item || !this.children.length || !this.overlayService)
5639
+ return;
5640
+ // Bindings live on leaf nodes inside the template slide, so recurse into the tree
5641
+ this.applyItemToNodes([this.children[0]], item);
5642
+ }
5643
+ applyItemToNodes(nodes, item) {
5644
+ for (const node of nodes) {
5645
+ if (node.bindings?.length) {
5646
+ const ref = this.overlayService.getNodeRef(node);
5647
+ if (ref?.componentRef) {
5648
+ for (const binding of node.bindings) {
5649
+ const value = resolvePath(item, binding.path);
5650
+ if (value !== undefined) {
5651
+ ref.componentRef.instance[binding.prop] = value;
5652
+ }
5653
+ }
5654
+ ref.componentRef.changeDetectorRef.detectChanges();
5655
+ }
5656
+ }
5657
+ if (node.children?.length) {
5658
+ this.applyItemToNodes(node.children, item);
5659
+ }
5660
+ }
5661
+ }
5662
+ _isInTemplate(target) {
5663
+ const search = (nodes) => nodes.some(c => c === target || c.id === target.id ||
5664
+ (c.children?.length ? search(c.children) : false));
5665
+ return this.children.length > 0 && search([this.children[0]]);
5666
+ }
5667
+ _scheduleReinit() {
5668
+ this._destroySwiper();
5669
+ this.cdr.detectChanges();
5670
+ this._initRafId = requestAnimationFrame(() => this._initSwiper());
5671
+ }
5672
+ // ── Swiper lifecycle ────────────────────────────────────────────────────────
5541
5673
  async _initSwiper() {
5542
5674
  const el = this.isPreview ? this.swiperEl?.nativeElement : this.editSwiperEl?.nativeElement;
5543
5675
  if (!el)
@@ -5671,44 +5803,87 @@ class SliderContainerComponent {
5671
5803
  if (eventName === 'swiperSlideChange') {
5672
5804
  const prevIdx = swiper.previousIndex;
5673
5805
  const activeIdx = swiper.activeIndex;
5674
- if (prevIdx !== undefined && this.children[prevIdx]) {
5675
- this.interactionEngine.replayTrigger(this.children[prevIdx].id, 'swiperSlideInactive');
5806
+ const allSlides = this._source ? [this.children[0], ...this.previewSlides] : this.children;
5807
+ if (prevIdx !== undefined && allSlides[prevIdx]) {
5808
+ this.interactionEngine.replayTrigger(allSlides[prevIdx].id, 'swiperSlideInactive');
5676
5809
  }
5677
- if (this.children[activeIdx]) {
5678
- this.interactionEngine.replayTrigger(this.children[activeIdx].id, 'swiperSlideActive');
5810
+ if (allSlides[activeIdx]) {
5811
+ this.interactionEngine.replayTrigger(allSlides[activeIdx].id, 'swiperSlideActive');
5679
5812
  }
5680
5813
  }
5681
5814
  }
5682
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SliderContainerComponent, deps: [{ token: DragEngineService }, { token: i0.ChangeDetectorRef }, { token: IS_PREVIEW, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
5683
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: SliderContainerComponent, isStandalone: false, selector: "app-slider-container", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", direction: "direction", slidesPerView: "slidesPerView", slidesPerGroup: "slidesPerGroup", loop: "loop", centeredSlides: "centeredSlides", spaceBetween: "spaceBetween", initialSlide: "initialSlide", autoHeight: "autoHeight", grabCursor: "grabCursor", slideToClickedSlide: "slideToClickedSlide", effect: "effect", speed: "speed", enableNavigation: "enableNavigation", navigationColor: "navigationColor", navigationSize: "navigationSize", enablePagination: "enablePagination", paginationColor: "paginationColor", paginationType: "paginationType", paginationClickable: "paginationClickable", paginationDynamicBullets: "paginationDynamicBullets", enableAutoplay: "enableAutoplay", autoplayDelay: "autoplayDelay", autoplayPauseOnMouseEnter: "autoplayPauseOnMouseEnter", autoplayDisableOnInteraction: "autoplayDisableOnInteraction", enableScrollbar: "enableScrollbar", freeMode: "freeMode", enableKeyboard: "enableKeyboard", enableMousewheel: "enableMousewheel", enableZoom: "enableZoom", enableParallax: "enableParallax", enableGrid: "enableGrid", gridRows: "gridRows", breakpoints: "breakpoints" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, viewQueries: [{ propertyName: "swiperEl", first: true, predicate: ["swiperEl"], descendants: true }, { propertyName: "editSwiperEl", first: true, predicate: ["editSwiperEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
5815
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SliderContainerComponent, deps: [{ token: DragEngineService }, { token: i0.ChangeDetectorRef }, { token: IS_PREVIEW, optional: true }, { token: OverlayService, optional: true }, { token: IOX_CONTENT_SERVICE, optional: true }, { token: IOX_SHOP_SERVICE, optional: true }, { token: DataSourceRegistryService, optional: true }, { token: PanelEventService, optional: true }, { token: i5.ActivatedRoute, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
5816
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: SliderContainerComponent, isStandalone: false, selector: "app-slider-container", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", direction: "direction", slidesPerView: "slidesPerView", slidesPerGroup: "slidesPerGroup", loop: "loop", centeredSlides: "centeredSlides", spaceBetween: "spaceBetween", initialSlide: "initialSlide", autoHeight: "autoHeight", grabCursor: "grabCursor", slideToClickedSlide: "slideToClickedSlide", effect: "effect", speed: "speed", enableNavigation: "enableNavigation", navigationColor: "navigationColor", navigationSize: "navigationSize", enablePagination: "enablePagination", paginationColor: "paginationColor", paginationType: "paginationType", paginationClickable: "paginationClickable", paginationDynamicBullets: "paginationDynamicBullets", enableAutoplay: "enableAutoplay", autoplayDelay: "autoplayDelay", autoplayPauseOnMouseEnter: "autoplayPauseOnMouseEnter", autoplayDisableOnInteraction: "autoplayDisableOnInteraction", enableScrollbar: "enableScrollbar", freeMode: "freeMode", enableKeyboard: "enableKeyboard", enableMousewheel: "enableMousewheel", enableZoom: "enableZoom", enableParallax: "enableParallax", enableGrid: "enableGrid", gridRows: "gridRows", breakpoints: "breakpoints", source: "source", limit: "limit" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, viewQueries: [{ propertyName: "swiperEl", first: true, predicate: ["swiperEl"], descendants: true }, { propertyName: "editSwiperEl", first: true, predicate: ["editSwiperEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
5684
5817
  <!-- EDIT MODE: shell wraps swiper + nav buttons so arrows position relative to slide area -->
5685
5818
  <div *ngIf="!isPreview"
5686
5819
  class="slider-edit-shell"
5687
5820
  [ngClass]="['iox-node-' + nodeId]"
5688
5821
  [ngStyle]="editShellStyle"
5689
5822
  ioxDropzone
5690
- [ioxDropzoneId]="dropListId"
5823
+ [ioxDropzoneId]="source ? '' : dropListId"
5691
5824
  [ioxDropzoneData]="children"
5692
5825
  (ioxDrop)="onDrop($event)">
5693
5826
 
5827
+ <!-- Repeat badge: hover-only, anchored top-left of shell -->
5828
+ <div *ngIf="source" class="slider-repeat-badge">
5829
+ <i class="ph-thin ph-repeat"></i>
5830
+ <span>{{ source }}</span>
5831
+ <span *ngIf="items.length" class="slider-repeat-badge-count">&times; {{ items.length }}</span>
5832
+ <i *ngIf="isLoading" class="ph-thin ph-circle-notch slider-badge-spin"></i>
5833
+ </div>
5834
+
5694
5835
  <div #editSwiperEl class="swiper swiper-edit">
5695
5836
  <div class="swiper-wrapper">
5696
- <div *ngFor="let child of children"
5697
- class="swiper-slide"
5698
- [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5699
- ioxDraggable
5700
- [ioxDragData]="child"
5701
- [ioxDragSourceId]="dropListId">
5702
- <ng-container
5703
- [ioxRender]="child"
5704
- (onClick)="childClick.emit($event)"
5705
- (onMouseEnter)="childMouseEnter.emit($event)"
5706
- (onMouseLeave)="childMouseLeave.emit()">
5707
- </ng-container>
5708
- </div>
5837
+
5838
+ <!-- NORMAL MODE: all children as draggable slides -->
5839
+ <ng-container *ngIf="!source">
5840
+ <div *ngFor="let child of children"
5841
+ class="swiper-slide"
5842
+ [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5843
+ ioxDraggable
5844
+ [ioxDragData]="child"
5845
+ [ioxDragSourceId]="dropListId">
5846
+ <ng-container
5847
+ [ioxRender]="child"
5848
+ (onClick)="childClick.emit($event)"
5849
+ (onMouseEnter)="childMouseEnter.emit($event)"
5850
+ (onMouseLeave)="childMouseLeave.emit()">
5851
+ </ng-container>
5852
+ </div>
5853
+ </ng-container>
5854
+
5855
+ <!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
5856
+ <ng-container *ngIf="source">
5857
+ <!-- Template slide — draggable/editable, receives item[0] data -->
5858
+ <div *ngIf="children.length"
5859
+ class="swiper-slide"
5860
+ [ngClass]="['iox-outer-' + (children[0].styleId ?? children[0].id)]"
5861
+ ioxDraggable
5862
+ [ioxDragData]="children[0]"
5863
+ [ioxDragSourceId]="dropListId">
5864
+ <ng-container
5865
+ [ioxRender]="children[0]"
5866
+ (onClick)="childClick.emit($event)"
5867
+ (onMouseEnter)="childMouseEnter.emit($event)"
5868
+ (onMouseLeave)="childMouseLeave.emit()">
5869
+ </ng-container>
5870
+ </div>
5871
+ <!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
5872
+ <div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
5873
+ class="swiper-slide slider-preview-slide"
5874
+ [ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
5875
+ <ng-container
5876
+ [ioxRender]="slide"
5877
+ (onClick)="$event"
5878
+ (onMouseEnter)="$event"
5879
+ (onMouseLeave)="undefined">
5880
+ </ng-container>
5881
+ </div>
5882
+ </ng-container>
5883
+
5709
5884
  </div>
5710
5885
 
5711
- <div *ngIf="!children.length" class="slider-edit-placeholder">
5886
+ <div *ngIf="!source && !children.length" class="slider-edit-placeholder">
5712
5887
  <i class="ph-thin ph-sliders-horizontal"></i>
5713
5888
  <span>Click + in toolbar to add a slide</span>
5714
5889
  </div>
@@ -5739,12 +5914,34 @@ class SliderContainerComponent {
5739
5914
  [style.--swiper-navigation-size.px]="navigationSize || null"
5740
5915
  [style.--swiper-pagination-color]="paginationColor || null">
5741
5916
  <div class="swiper-wrapper">
5742
- <ng-container *ngFor="let child of children">
5743
- <ng-container
5744
- [ioxRender]="child"
5745
- (onClick)="childClick.emit($event)"
5746
- (onMouseEnter)="childMouseEnter.emit($event)"
5747
- (onMouseLeave)="childMouseLeave.emit()">
5917
+ <!-- NORMAL: all children (each SliderSlide renders .swiper-slide itself in preview mode) -->
5918
+ <ng-container *ngIf="!source">
5919
+ <ng-container *ngFor="let child of children">
5920
+ <ng-container
5921
+ [ioxRender]="child"
5922
+ (onClick)="childClick.emit($event)"
5923
+ (onMouseEnter)="childMouseEnter.emit($event)"
5924
+ (onMouseLeave)="childMouseLeave.emit()">
5925
+ </ng-container>
5926
+ </ng-container>
5927
+ </ng-container>
5928
+ <!-- REPEAT: template slide + preview slides -->
5929
+ <ng-container *ngIf="source">
5930
+ <ng-container *ngIf="children.length">
5931
+ <ng-container
5932
+ [ioxRender]="children[0]"
5933
+ (onClick)="childClick.emit($event)"
5934
+ (onMouseEnter)="childMouseEnter.emit($event)"
5935
+ (onMouseLeave)="childMouseLeave.emit()">
5936
+ </ng-container>
5937
+ </ng-container>
5938
+ <ng-container *ngFor="let slide of previewSlides; trackBy: trackByNodeId">
5939
+ <ng-container
5940
+ [ioxRender]="slide"
5941
+ (onClick)="childClick.emit($event)"
5942
+ (onMouseEnter)="childMouseEnter.emit($event)"
5943
+ (onMouseLeave)="childMouseLeave.emit()">
5944
+ </ng-container>
5748
5945
  </ng-container>
5749
5946
  </ng-container>
5750
5947
  </div>
@@ -5753,7 +5950,7 @@ class SliderContainerComponent {
5753
5950
  <div *ngIf="enablePagination" class="swiper-pagination"></div>
5754
5951
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
5755
5952
  </div>
5756
- `, isInline: true, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
5953
+ `, isInline: true, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}.slider-repeat-badge{position:absolute;top:0;left:0;z-index:10;display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px solid rgba(203,144,144,.5);border-radius:0 0 4px;opacity:0;transition:opacity .15s;-webkit-user-select:none;user-select:none;pointer-events:none}.slider-edit-shell:hover .slider-repeat-badge{opacity:1}.slider-repeat-badge-count{font-weight:600}.slider-badge-spin{animation:slider-badge-spin 1s linear infinite}@keyframes slider-badge-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.slider-preview-slide{opacity:.55;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
5757
5954
  }
5758
5955
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SliderContainerComponent, decorators: [{
5759
5956
  type: Component,
@@ -5764,28 +5961,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5764
5961
  [ngClass]="['iox-node-' + nodeId]"
5765
5962
  [ngStyle]="editShellStyle"
5766
5963
  ioxDropzone
5767
- [ioxDropzoneId]="dropListId"
5964
+ [ioxDropzoneId]="source ? '' : dropListId"
5768
5965
  [ioxDropzoneData]="children"
5769
5966
  (ioxDrop)="onDrop($event)">
5770
5967
 
5968
+ <!-- Repeat badge: hover-only, anchored top-left of shell -->
5969
+ <div *ngIf="source" class="slider-repeat-badge">
5970
+ <i class="ph-thin ph-repeat"></i>
5971
+ <span>{{ source }}</span>
5972
+ <span *ngIf="items.length" class="slider-repeat-badge-count">&times; {{ items.length }}</span>
5973
+ <i *ngIf="isLoading" class="ph-thin ph-circle-notch slider-badge-spin"></i>
5974
+ </div>
5975
+
5771
5976
  <div #editSwiperEl class="swiper swiper-edit">
5772
5977
  <div class="swiper-wrapper">
5773
- <div *ngFor="let child of children"
5774
- class="swiper-slide"
5775
- [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5776
- ioxDraggable
5777
- [ioxDragData]="child"
5778
- [ioxDragSourceId]="dropListId">
5779
- <ng-container
5780
- [ioxRender]="child"
5781
- (onClick)="childClick.emit($event)"
5782
- (onMouseEnter)="childMouseEnter.emit($event)"
5783
- (onMouseLeave)="childMouseLeave.emit()">
5784
- </ng-container>
5785
- </div>
5978
+
5979
+ <!-- NORMAL MODE: all children as draggable slides -->
5980
+ <ng-container *ngIf="!source">
5981
+ <div *ngFor="let child of children"
5982
+ class="swiper-slide"
5983
+ [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5984
+ ioxDraggable
5985
+ [ioxDragData]="child"
5986
+ [ioxDragSourceId]="dropListId">
5987
+ <ng-container
5988
+ [ioxRender]="child"
5989
+ (onClick)="childClick.emit($event)"
5990
+ (onMouseEnter)="childMouseEnter.emit($event)"
5991
+ (onMouseLeave)="childMouseLeave.emit()">
5992
+ </ng-container>
5993
+ </div>
5994
+ </ng-container>
5995
+
5996
+ <!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
5997
+ <ng-container *ngIf="source">
5998
+ <!-- Template slide — draggable/editable, receives item[0] data -->
5999
+ <div *ngIf="children.length"
6000
+ class="swiper-slide"
6001
+ [ngClass]="['iox-outer-' + (children[0].styleId ?? children[0].id)]"
6002
+ ioxDraggable
6003
+ [ioxDragData]="children[0]"
6004
+ [ioxDragSourceId]="dropListId">
6005
+ <ng-container
6006
+ [ioxRender]="children[0]"
6007
+ (onClick)="childClick.emit($event)"
6008
+ (onMouseEnter)="childMouseEnter.emit($event)"
6009
+ (onMouseLeave)="childMouseLeave.emit()">
6010
+ </ng-container>
6011
+ </div>
6012
+ <!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
6013
+ <div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
6014
+ class="swiper-slide slider-preview-slide"
6015
+ [ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
6016
+ <ng-container
6017
+ [ioxRender]="slide"
6018
+ (onClick)="$event"
6019
+ (onMouseEnter)="$event"
6020
+ (onMouseLeave)="undefined">
6021
+ </ng-container>
6022
+ </div>
6023
+ </ng-container>
6024
+
5786
6025
  </div>
5787
6026
 
5788
- <div *ngIf="!children.length" class="slider-edit-placeholder">
6027
+ <div *ngIf="!source && !children.length" class="slider-edit-placeholder">
5789
6028
  <i class="ph-thin ph-sliders-horizontal"></i>
5790
6029
  <span>Click + in toolbar to add a slide</span>
5791
6030
  </div>
@@ -5816,12 +6055,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5816
6055
  [style.--swiper-navigation-size.px]="navigationSize || null"
5817
6056
  [style.--swiper-pagination-color]="paginationColor || null">
5818
6057
  <div class="swiper-wrapper">
5819
- <ng-container *ngFor="let child of children">
5820
- <ng-container
5821
- [ioxRender]="child"
5822
- (onClick)="childClick.emit($event)"
5823
- (onMouseEnter)="childMouseEnter.emit($event)"
5824
- (onMouseLeave)="childMouseLeave.emit()">
6058
+ <!-- NORMAL: all children (each SliderSlide renders .swiper-slide itself in preview mode) -->
6059
+ <ng-container *ngIf="!source">
6060
+ <ng-container *ngFor="let child of children">
6061
+ <ng-container
6062
+ [ioxRender]="child"
6063
+ (onClick)="childClick.emit($event)"
6064
+ (onMouseEnter)="childMouseEnter.emit($event)"
6065
+ (onMouseLeave)="childMouseLeave.emit()">
6066
+ </ng-container>
6067
+ </ng-container>
6068
+ </ng-container>
6069
+ <!-- REPEAT: template slide + preview slides -->
6070
+ <ng-container *ngIf="source">
6071
+ <ng-container *ngIf="children.length">
6072
+ <ng-container
6073
+ [ioxRender]="children[0]"
6074
+ (onClick)="childClick.emit($event)"
6075
+ (onMouseEnter)="childMouseEnter.emit($event)"
6076
+ (onMouseLeave)="childMouseLeave.emit()">
6077
+ </ng-container>
6078
+ </ng-container>
6079
+ <ng-container *ngFor="let slide of previewSlides; trackBy: trackByNodeId">
6080
+ <ng-container
6081
+ [ioxRender]="slide"
6082
+ (onClick)="childClick.emit($event)"
6083
+ (onMouseEnter)="childMouseEnter.emit($event)"
6084
+ (onMouseLeave)="childMouseLeave.emit()">
6085
+ </ng-container>
5825
6086
  </ng-container>
5826
6087
  </ng-container>
5827
6088
  </div>
@@ -5830,12 +6091,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5830
6091
  <div *ngIf="enablePagination" class="swiper-pagination"></div>
5831
6092
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
5832
6093
  </div>
5833
- `, standalone: false, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}\n"] }]
6094
+ `, standalone: false, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}.slider-repeat-badge{position:absolute;top:0;left:0;z-index:10;display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px solid rgba(203,144,144,.5);border-radius:0 0 4px;opacity:0;transition:opacity .15s;-webkit-user-select:none;user-select:none;pointer-events:none}.slider-edit-shell:hover .slider-repeat-badge{opacity:1}.slider-repeat-badge-count{font-weight:600}.slider-badge-spin{animation:slider-badge-spin 1s linear infinite}@keyframes slider-badge-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.slider-preview-slide{opacity:.55;pointer-events:none}\n"] }]
5834
6095
  }], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
5835
6096
  type: Optional
5836
6097
  }, {
5837
6098
  type: Inject,
5838
6099
  args: [IS_PREVIEW]
6100
+ }] }, { type: OverlayService, decorators: [{
6101
+ type: Optional
6102
+ }] }, { type: undefined, decorators: [{
6103
+ type: Optional
6104
+ }, {
6105
+ type: Inject,
6106
+ args: [IOX_CONTENT_SERVICE]
6107
+ }] }, { type: undefined, decorators: [{
6108
+ type: Optional
6109
+ }, {
6110
+ type: Inject,
6111
+ args: [IOX_SHOP_SERVICE]
6112
+ }] }, { type: DataSourceRegistryService, decorators: [{
6113
+ type: Optional
6114
+ }] }, { type: PanelEventService, decorators: [{
6115
+ type: Optional
6116
+ }] }, { type: i5.ActivatedRoute, decorators: [{
6117
+ type: Optional
5839
6118
  }] }], propDecorators: { children: [{
5840
6119
  type: Input
5841
6120
  }], style: [{
@@ -5910,6 +6189,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5910
6189
  type: Input
5911
6190
  }], breakpoints: [{
5912
6191
  type: Input
6192
+ }], source: [{
6193
+ type: Input
6194
+ }], limit: [{
6195
+ type: Input
5913
6196
  }], childClick: [{
5914
6197
  type: Output
5915
6198
  }], childMouseEnter: [{
@@ -6988,6 +7271,16 @@ class SliderContainerConfig extends ComponentConfig {
6988
7271
  { value: 'swiperAutoplayPause', label: 'Pause Autoplay' },
6989
7272
  ];
6990
7273
  this.traits = [
7274
+ // ── Data binding ──────────────────────────────────────────────────
7275
+ new TraitConfig('__dataLabel', 'Data', TraitInputType.SectionLabel),
7276
+ {
7277
+ name: 'source',
7278
+ label: 'Source',
7279
+ type: TraitInputType.Select,
7280
+ options: [], // populated at runtime from DataSourceRegistryService aliases
7281
+ default: '',
7282
+ },
7283
+ new TraitConfig('limit', 'Limit', TraitInputType.Number, undefined, 10),
6991
7284
  // ── Parameters ────────────────────────────────────────────────────
6992
7285
  new TraitConfig('__paramsLabel', 'Parameters', TraitInputType.SectionLabel),
6993
7286
  new TraitConfig('direction', 'Slide direction', TraitInputType.Select, [{ label: 'Horizontal', value: 'horizontal' }, { label: 'Vertical', value: 'vertical' }], 'horizontal'),