@vectoriox/iox-builder 1.4.92 → 1.4.94

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,93 @@ class SliderContainerComponent {
5530
5580
  ngOnDestroy() {
5531
5581
  cancelAnimationFrame(this._initRafId);
5532
5582
  this._destroySwiper();
5583
+ this._bindingSub?.unsubscribe();
5533
5584
  }
5534
5585
  onDrop(event) {
5586
+ if (this._source)
5587
+ return; // repeat mode: drops disabled, slides come from data source
5535
5588
  this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
5536
5589
  // Sync Swiper's internal slide list after drag-drop reorder
5537
5590
  if (this._swiper) {
5538
5591
  requestAnimationFrame(() => this._swiper?.update());
5539
5592
  }
5540
5593
  }
5594
+ // ── Data fetching ───────────────────────────────────────────────────────────
5595
+ fetchData() {
5596
+ if (!this._source || !this.dsRegistry)
5597
+ return;
5598
+ const ds = this.dsRegistry.findByAlias(this._source);
5599
+ const isShop = ds?.type === DataSourceType.Shop;
5600
+ const contentType = isShop ? ds?.request?.shop?.contentType : ds?.request?.cms?.contentType;
5601
+ const service = isShop ? this.shopContentService : this.contentService;
5602
+ if (!contentType || !service)
5603
+ return;
5604
+ this.isLoading = true;
5605
+ this.cdr.markForCheck();
5606
+ service.getContent(contentType, this._orgId).subscribe({
5607
+ next: (data) => {
5608
+ const all = Array.isArray(data) ? data : (data?.items || data?.data || []);
5609
+ this.items = all.slice(0, this._limit);
5610
+ this.refreshPreviews();
5611
+ this.isLoading = false;
5612
+ this.cdr.markForCheck();
5613
+ },
5614
+ error: () => {
5615
+ this.isLoading = false;
5616
+ this.cdr.markForCheck();
5617
+ },
5618
+ });
5619
+ }
5620
+ refreshPreviews() {
5621
+ if (!this.items.length || !this.children.length) {
5622
+ this.previewSlides = [];
5623
+ this.cdr.markForCheck();
5624
+ this._scheduleReinit();
5625
+ return;
5626
+ }
5627
+ this.applyFirstItemToTemplate();
5628
+ // Each preview slide = one cloned SliderSlide node with item data resolved
5629
+ this.previewSlides = this.items.slice(1).map(item => cloneNodesForItem([this.children[0]], item)[0]);
5630
+ this.cdr.markForCheck();
5631
+ this._scheduleReinit();
5632
+ }
5633
+ applyFirstItemToTemplate() {
5634
+ const item = this.items[0];
5635
+ if (!item || !this.children.length || !this.overlayService)
5636
+ return;
5637
+ // Bindings live on leaf nodes inside the template slide, so recurse into the tree
5638
+ this.applyItemToNodes([this.children[0]], item);
5639
+ }
5640
+ applyItemToNodes(nodes, item) {
5641
+ for (const node of nodes) {
5642
+ if (node.bindings?.length) {
5643
+ const ref = this.overlayService.getNodeRef(node);
5644
+ if (ref?.componentRef) {
5645
+ for (const binding of node.bindings) {
5646
+ const value = resolvePath(item, binding.path);
5647
+ if (value !== undefined) {
5648
+ ref.componentRef.instance[binding.prop] = value;
5649
+ }
5650
+ }
5651
+ ref.componentRef.changeDetectorRef.detectChanges();
5652
+ }
5653
+ }
5654
+ if (node.children?.length) {
5655
+ this.applyItemToNodes(node.children, item);
5656
+ }
5657
+ }
5658
+ }
5659
+ _isInTemplate(target) {
5660
+ const search = (nodes) => nodes.some(c => c === target || c.id === target.id ||
5661
+ (c.children?.length ? search(c.children) : false));
5662
+ return this.children.length > 0 && search([this.children[0]]);
5663
+ }
5664
+ _scheduleReinit() {
5665
+ this._destroySwiper();
5666
+ this.cdr.detectChanges();
5667
+ this._initRafId = requestAnimationFrame(() => this._initSwiper());
5668
+ }
5669
+ // ── Swiper lifecycle ────────────────────────────────────────────────────────
5541
5670
  async _initSwiper() {
5542
5671
  const el = this.isPreview ? this.swiperEl?.nativeElement : this.editSwiperEl?.nativeElement;
5543
5672
  if (!el)
@@ -5671,44 +5800,87 @@ class SliderContainerComponent {
5671
5800
  if (eventName === 'swiperSlideChange') {
5672
5801
  const prevIdx = swiper.previousIndex;
5673
5802
  const activeIdx = swiper.activeIndex;
5674
- if (prevIdx !== undefined && this.children[prevIdx]) {
5675
- this.interactionEngine.replayTrigger(this.children[prevIdx].id, 'swiperSlideInactive');
5803
+ const allSlides = this._source ? [this.children[0], ...this.previewSlides] : this.children;
5804
+ if (prevIdx !== undefined && allSlides[prevIdx]) {
5805
+ this.interactionEngine.replayTrigger(allSlides[prevIdx].id, 'swiperSlideInactive');
5676
5806
  }
5677
- if (this.children[activeIdx]) {
5678
- this.interactionEngine.replayTrigger(this.children[activeIdx].id, 'swiperSlideActive');
5807
+ if (allSlides[activeIdx]) {
5808
+ this.interactionEngine.replayTrigger(allSlides[activeIdx].id, 'swiperSlideActive');
5679
5809
  }
5680
5810
  }
5681
5811
  }
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: `
5812
+ 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 }); }
5813
+ 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
5814
  <!-- EDIT MODE: shell wraps swiper + nav buttons so arrows position relative to slide area -->
5685
5815
  <div *ngIf="!isPreview"
5686
5816
  class="slider-edit-shell"
5687
5817
  [ngClass]="['iox-node-' + nodeId]"
5688
5818
  [ngStyle]="editShellStyle"
5689
5819
  ioxDropzone
5690
- [ioxDropzoneId]="dropListId"
5820
+ [ioxDropzoneId]="source ? '' : dropListId"
5691
5821
  [ioxDropzoneData]="children"
5692
5822
  (ioxDrop)="onDrop($event)">
5693
5823
 
5824
+ <!-- Repeat badge: hover-only, anchored top-left of shell -->
5825
+ <div *ngIf="source" class="slider-repeat-badge">
5826
+ <i class="ph-thin ph-repeat"></i>
5827
+ <span>{{ source }}</span>
5828
+ <span *ngIf="items.length" class="slider-repeat-badge-count">&times; {{ items.length }}</span>
5829
+ <i *ngIf="isLoading" class="ph-thin ph-circle-notch slider-badge-spin"></i>
5830
+ </div>
5831
+
5694
5832
  <div #editSwiperEl class="swiper swiper-edit">
5695
5833
  <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>
5834
+
5835
+ <!-- NORMAL MODE: all children as draggable slides -->
5836
+ <ng-container *ngIf="!source">
5837
+ <div *ngFor="let child of children"
5838
+ class="swiper-slide"
5839
+ [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5840
+ ioxDraggable
5841
+ [ioxDragData]="child"
5842
+ [ioxDragSourceId]="dropListId">
5843
+ <ng-container
5844
+ [ioxRender]="child"
5845
+ (onClick)="childClick.emit($event)"
5846
+ (onMouseEnter)="childMouseEnter.emit($event)"
5847
+ (onMouseLeave)="childMouseLeave.emit()">
5848
+ </ng-container>
5849
+ </div>
5850
+ </ng-container>
5851
+
5852
+ <!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
5853
+ <ng-container *ngIf="source">
5854
+ <!-- Template slide — draggable/editable, receives item[0] data -->
5855
+ <div *ngIf="children.length"
5856
+ class="swiper-slide"
5857
+ [ngClass]="['iox-outer-' + (children[0].styleId ?? children[0].id)]"
5858
+ ioxDraggable
5859
+ [ioxDragData]="children[0]"
5860
+ [ioxDragSourceId]="dropListId">
5861
+ <ng-container
5862
+ [ioxRender]="children[0]"
5863
+ (onClick)="childClick.emit($event)"
5864
+ (onMouseEnter)="childMouseEnter.emit($event)"
5865
+ (onMouseLeave)="childMouseLeave.emit()">
5866
+ </ng-container>
5867
+ </div>
5868
+ <!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
5869
+ <div *ngFor="let slide of previewSlides"
5870
+ class="swiper-slide slider-preview-slide"
5871
+ [ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
5872
+ <ng-container
5873
+ [ioxRender]="slide"
5874
+ (onClick)="$event"
5875
+ (onMouseEnter)="$event"
5876
+ (onMouseLeave)="undefined">
5877
+ </ng-container>
5878
+ </div>
5879
+ </ng-container>
5880
+
5709
5881
  </div>
5710
5882
 
5711
- <div *ngIf="!children.length" class="slider-edit-placeholder">
5883
+ <div *ngIf="!source && !children.length" class="slider-edit-placeholder">
5712
5884
  <i class="ph-thin ph-sliders-horizontal"></i>
5713
5885
  <span>Click + in toolbar to add a slide</span>
5714
5886
  </div>
@@ -5739,12 +5911,34 @@ class SliderContainerComponent {
5739
5911
  [style.--swiper-navigation-size.px]="navigationSize || null"
5740
5912
  [style.--swiper-pagination-color]="paginationColor || null">
5741
5913
  <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()">
5914
+ <!-- NORMAL: all children (each SliderSlide renders .swiper-slide itself in preview mode) -->
5915
+ <ng-container *ngIf="!source">
5916
+ <ng-container *ngFor="let child of children">
5917
+ <ng-container
5918
+ [ioxRender]="child"
5919
+ (onClick)="childClick.emit($event)"
5920
+ (onMouseEnter)="childMouseEnter.emit($event)"
5921
+ (onMouseLeave)="childMouseLeave.emit()">
5922
+ </ng-container>
5923
+ </ng-container>
5924
+ </ng-container>
5925
+ <!-- REPEAT: template slide + preview slides -->
5926
+ <ng-container *ngIf="source">
5927
+ <ng-container *ngIf="children.length">
5928
+ <ng-container
5929
+ [ioxRender]="children[0]"
5930
+ (onClick)="childClick.emit($event)"
5931
+ (onMouseEnter)="childMouseEnter.emit($event)"
5932
+ (onMouseLeave)="childMouseLeave.emit()">
5933
+ </ng-container>
5934
+ </ng-container>
5935
+ <ng-container *ngFor="let slide of previewSlides">
5936
+ <ng-container
5937
+ [ioxRender]="slide"
5938
+ (onClick)="childClick.emit($event)"
5939
+ (onMouseEnter)="childMouseEnter.emit($event)"
5940
+ (onMouseLeave)="childMouseLeave.emit()">
5941
+ </ng-container>
5748
5942
  </ng-container>
5749
5943
  </ng-container>
5750
5944
  </div>
@@ -5753,7 +5947,7 @@ class SliderContainerComponent {
5753
5947
  <div *ngIf="enablePagination" class="swiper-pagination"></div>
5754
5948
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
5755
5949
  </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"] }] }); }
5950
+ `, 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
5951
  }
5758
5952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SliderContainerComponent, decorators: [{
5759
5953
  type: Component,
@@ -5764,28 +5958,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5764
5958
  [ngClass]="['iox-node-' + nodeId]"
5765
5959
  [ngStyle]="editShellStyle"
5766
5960
  ioxDropzone
5767
- [ioxDropzoneId]="dropListId"
5961
+ [ioxDropzoneId]="source ? '' : dropListId"
5768
5962
  [ioxDropzoneData]="children"
5769
5963
  (ioxDrop)="onDrop($event)">
5770
5964
 
5965
+ <!-- Repeat badge: hover-only, anchored top-left of shell -->
5966
+ <div *ngIf="source" class="slider-repeat-badge">
5967
+ <i class="ph-thin ph-repeat"></i>
5968
+ <span>{{ source }}</span>
5969
+ <span *ngIf="items.length" class="slider-repeat-badge-count">&times; {{ items.length }}</span>
5970
+ <i *ngIf="isLoading" class="ph-thin ph-circle-notch slider-badge-spin"></i>
5971
+ </div>
5972
+
5771
5973
  <div #editSwiperEl class="swiper swiper-edit">
5772
5974
  <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>
5975
+
5976
+ <!-- NORMAL MODE: all children as draggable slides -->
5977
+ <ng-container *ngIf="!source">
5978
+ <div *ngFor="let child of children"
5979
+ class="swiper-slide"
5980
+ [ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
5981
+ ioxDraggable
5982
+ [ioxDragData]="child"
5983
+ [ioxDragSourceId]="dropListId">
5984
+ <ng-container
5985
+ [ioxRender]="child"
5986
+ (onClick)="childClick.emit($event)"
5987
+ (onMouseEnter)="childMouseEnter.emit($event)"
5988
+ (onMouseLeave)="childMouseLeave.emit()">
5989
+ </ng-container>
5990
+ </div>
5991
+ </ng-container>
5992
+
5993
+ <!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
5994
+ <ng-container *ngIf="source">
5995
+ <!-- Template slide — draggable/editable, receives item[0] data -->
5996
+ <div *ngIf="children.length"
5997
+ class="swiper-slide"
5998
+ [ngClass]="['iox-outer-' + (children[0].styleId ?? children[0].id)]"
5999
+ ioxDraggable
6000
+ [ioxDragData]="children[0]"
6001
+ [ioxDragSourceId]="dropListId">
6002
+ <ng-container
6003
+ [ioxRender]="children[0]"
6004
+ (onClick)="childClick.emit($event)"
6005
+ (onMouseEnter)="childMouseEnter.emit($event)"
6006
+ (onMouseLeave)="childMouseLeave.emit()">
6007
+ </ng-container>
6008
+ </div>
6009
+ <!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
6010
+ <div *ngFor="let slide of previewSlides"
6011
+ class="swiper-slide slider-preview-slide"
6012
+ [ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
6013
+ <ng-container
6014
+ [ioxRender]="slide"
6015
+ (onClick)="$event"
6016
+ (onMouseEnter)="$event"
6017
+ (onMouseLeave)="undefined">
6018
+ </ng-container>
6019
+ </div>
6020
+ </ng-container>
6021
+
5786
6022
  </div>
5787
6023
 
5788
- <div *ngIf="!children.length" class="slider-edit-placeholder">
6024
+ <div *ngIf="!source && !children.length" class="slider-edit-placeholder">
5789
6025
  <i class="ph-thin ph-sliders-horizontal"></i>
5790
6026
  <span>Click + in toolbar to add a slide</span>
5791
6027
  </div>
@@ -5816,12 +6052,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5816
6052
  [style.--swiper-navigation-size.px]="navigationSize || null"
5817
6053
  [style.--swiper-pagination-color]="paginationColor || null">
5818
6054
  <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()">
6055
+ <!-- NORMAL: all children (each SliderSlide renders .swiper-slide itself in preview mode) -->
6056
+ <ng-container *ngIf="!source">
6057
+ <ng-container *ngFor="let child of children">
6058
+ <ng-container
6059
+ [ioxRender]="child"
6060
+ (onClick)="childClick.emit($event)"
6061
+ (onMouseEnter)="childMouseEnter.emit($event)"
6062
+ (onMouseLeave)="childMouseLeave.emit()">
6063
+ </ng-container>
6064
+ </ng-container>
6065
+ </ng-container>
6066
+ <!-- REPEAT: template slide + preview slides -->
6067
+ <ng-container *ngIf="source">
6068
+ <ng-container *ngIf="children.length">
6069
+ <ng-container
6070
+ [ioxRender]="children[0]"
6071
+ (onClick)="childClick.emit($event)"
6072
+ (onMouseEnter)="childMouseEnter.emit($event)"
6073
+ (onMouseLeave)="childMouseLeave.emit()">
6074
+ </ng-container>
6075
+ </ng-container>
6076
+ <ng-container *ngFor="let slide of previewSlides">
6077
+ <ng-container
6078
+ [ioxRender]="slide"
6079
+ (onClick)="childClick.emit($event)"
6080
+ (onMouseEnter)="childMouseEnter.emit($event)"
6081
+ (onMouseLeave)="childMouseLeave.emit()">
6082
+ </ng-container>
5825
6083
  </ng-container>
5826
6084
  </ng-container>
5827
6085
  </div>
@@ -5830,12 +6088,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5830
6088
  <div *ngIf="enablePagination" class="swiper-pagination"></div>
5831
6089
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
5832
6090
  </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"] }]
6091
+ `, 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
6092
  }], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
5835
6093
  type: Optional
5836
6094
  }, {
5837
6095
  type: Inject,
5838
6096
  args: [IS_PREVIEW]
6097
+ }] }, { type: OverlayService, decorators: [{
6098
+ type: Optional
6099
+ }] }, { type: undefined, decorators: [{
6100
+ type: Optional
6101
+ }, {
6102
+ type: Inject,
6103
+ args: [IOX_CONTENT_SERVICE]
6104
+ }] }, { type: undefined, decorators: [{
6105
+ type: Optional
6106
+ }, {
6107
+ type: Inject,
6108
+ args: [IOX_SHOP_SERVICE]
6109
+ }] }, { type: DataSourceRegistryService, decorators: [{
6110
+ type: Optional
6111
+ }] }, { type: PanelEventService, decorators: [{
6112
+ type: Optional
6113
+ }] }, { type: i5.ActivatedRoute, decorators: [{
6114
+ type: Optional
5839
6115
  }] }], propDecorators: { children: [{
5840
6116
  type: Input
5841
6117
  }], style: [{
@@ -5910,6 +6186,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5910
6186
  type: Input
5911
6187
  }], breakpoints: [{
5912
6188
  type: Input
6189
+ }], source: [{
6190
+ type: Input
6191
+ }], limit: [{
6192
+ type: Input
5913
6193
  }], childClick: [{
5914
6194
  type: Output
5915
6195
  }], childMouseEnter: [{
@@ -6009,7 +6289,7 @@ class SliderSlideComponent {
6009
6289
  </ng-container>
6010
6290
  </div>
6011
6291
  </div>
6012
- `, isInline: true, styles: [".swiper-slide,.slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-200)}.slide-edit-block{width:100%}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-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"] }] }); }
6292
+ `, isInline: true, styles: [".swiper-slide,.slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-300)}.slide-edit-block{width:100%}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-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"] }] }); }
6013
6293
  }
6014
6294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SliderSlideComponent, decorators: [{
6015
6295
  type: Component,
@@ -6074,7 +6354,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6074
6354
  </ng-container>
6075
6355
  </div>
6076
6356
  </div>
6077
- `, standalone: false, styles: [".swiper-slide,.slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-200)}.slide-edit-block{width:100%}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-placeholder i{font-size:22px}\n"] }]
6357
+ `, standalone: false, styles: [".swiper-slide,.slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-300)}.slide-edit-block{width:100%}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-placeholder i{font-size:22px}\n"] }]
6078
6358
  }], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
6079
6359
  type: Optional
6080
6360
  }, {
@@ -6988,6 +7268,16 @@ class SliderContainerConfig extends ComponentConfig {
6988
7268
  { value: 'swiperAutoplayPause', label: 'Pause Autoplay' },
6989
7269
  ];
6990
7270
  this.traits = [
7271
+ // ── Data binding ──────────────────────────────────────────────────
7272
+ new TraitConfig('__dataLabel', 'Data', TraitInputType.SectionLabel),
7273
+ {
7274
+ name: 'source',
7275
+ label: 'Source',
7276
+ type: TraitInputType.Select,
7277
+ options: [], // populated at runtime from DataSourceRegistryService aliases
7278
+ default: '',
7279
+ },
7280
+ new TraitConfig('limit', 'Limit', TraitInputType.Number, undefined, 10),
6991
7281
  // ── Parameters ────────────────────────────────────────────────────
6992
7282
  new TraitConfig('__paramsLabel', 'Parameters', TraitInputType.SectionLabel),
6993
7283
  new TraitConfig('direction', 'Slide direction', TraitInputType.Select, [{ label: 'Horizontal', value: 'horizontal' }, { label: 'Vertical', value: 'vertical' }], 'horizontal'),