@vectoriox/iox-builder 2.11.0 → 2.12.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 { Injectable, inject, NgZone, EventEmitter, createComponent, Output, Input, Inject, Directive, ChangeDetectionStrategy, Component, ViewChild, Optional, InjectionToken, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
|
|
2
|
+
import { Injectable, inject, NgZone, EventEmitter, createComponent, Output, Input, Inject, Directive, ChangeDetectionStrategy, Component, ViewChild, effect, Optional, InjectionToken, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
5
5
|
import * as i4$1 from '@angular/forms';
|
|
@@ -17,7 +17,7 @@ import { SelectModule } from 'primeng/select';
|
|
|
17
17
|
import { TooltipModule } from 'primeng/tooltip';
|
|
18
18
|
import { DialogModule } from 'primeng/dialog';
|
|
19
19
|
import * as i16 from '@vectoriox/iox-ui';
|
|
20
|
-
import { DEFAULT_WIDTH_BY_TYPE, ComponentInstanceRegistryService, buildTargetedStateSelector, compileDeclarations, safeSetInput, composeVirtualTraits,
|
|
20
|
+
import { DEFAULT_WIDTH_BY_TYPE, ContextStore, ComponentInstanceRegistryService, isContextAction, contextActionEffect, resolveContextValue, buildTargetedStateSelector, compileDeclarations, safeSetInput, composeVirtualTraits, collectContextSlotKeys, withContextSlots, nodeUsesContext, resolvePath, applyBindingPipes, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule } from '@vectoriox/iox-ui';
|
|
21
21
|
export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
|
|
22
22
|
import Lenis from 'lenis';
|
|
23
23
|
import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
|
|
@@ -2027,6 +2027,10 @@ class InteractionEngineService {
|
|
|
2027
2027
|
constructor(overlayService) {
|
|
2028
2028
|
this.overlayService = overlayService;
|
|
2029
2029
|
this.attached = new Map();
|
|
2030
|
+
/** Shared with the SSR engine — one store type, one set of semantics. */
|
|
2031
|
+
this.contextStore = inject(ContextStore);
|
|
2032
|
+
/** Resolved data for the canvas, so a row marker (source + index) resolves back to its item. */
|
|
2033
|
+
this.contextData = {};
|
|
2030
2034
|
/** Elements that have had pre-state applied (hidden by attach). */
|
|
2031
2035
|
this.preStatedElements = new Set();
|
|
2032
2036
|
/**
|
|
@@ -2372,6 +2376,12 @@ class InteractionEngineService {
|
|
|
2372
2376
|
const leaveHandler = () => {
|
|
2373
2377
|
// Reverse animations on leave for hover triggers
|
|
2374
2378
|
for (const action of ix.actions) {
|
|
2379
|
+
// Context actions have no "backwards" — the shared table decides what leave does
|
|
2380
|
+
// (clear, unless the action sets noReverse). Same call the SSR engine makes.
|
|
2381
|
+
if (isContextAction(action)) {
|
|
2382
|
+
this.applyContextAction(action, element, 'reverse');
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2375
2385
|
const target = this.resolveTarget(node, action, element);
|
|
2376
2386
|
if (!target)
|
|
2377
2387
|
continue;
|
|
@@ -2392,6 +2402,13 @@ class InteractionEngineService {
|
|
|
2392
2402
|
this.executeSwiperAction(action);
|
|
2393
2403
|
continue;
|
|
2394
2404
|
}
|
|
2405
|
+
// Context actions write STATE, not animation. `selfEl` is deliberate: $item is read by
|
|
2406
|
+
// walking up from the element actually hovered/clicked, which for a repeater PREVIEW ROW
|
|
2407
|
+
// is the clone — so each row publishes its own item, exactly as on the live site.
|
|
2408
|
+
if (isContextAction(action)) {
|
|
2409
|
+
this.applyContextAction(action, selfEl ?? null, 'forward');
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2395
2412
|
// selfEl lets 'self'-targeted actions resolve for preview CLONES, which aren't
|
|
2396
2413
|
// overlay-registered (getNodeRef would return undefined) — see attachTo. (issue #2)
|
|
2397
2414
|
const target = this.resolveTarget(node, action, selfEl);
|
|
@@ -2400,6 +2417,40 @@ class InteractionEngineService {
|
|
|
2400
2417
|
this.executeAction(target, action);
|
|
2401
2418
|
}
|
|
2402
2419
|
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Execute a context action against the shared store. The forward/reverse semantics come from
|
|
2422
|
+
* `contextActionEffect` in @vectoriox/iox-ui — the same function the SSR engine calls, so the
|
|
2423
|
+
* builder canvas and the live site behave identically.
|
|
2424
|
+
*/
|
|
2425
|
+
applyContextAction(action, el, pass) {
|
|
2426
|
+
const effect = contextActionEffect(action, pass);
|
|
2427
|
+
if (!effect)
|
|
2428
|
+
return;
|
|
2429
|
+
if (effect.op === 'clear') {
|
|
2430
|
+
this.contextStore.clear(effect.slot);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const value = resolveContextValue(effect.value, {
|
|
2434
|
+
item: this.rowItemFor(el),
|
|
2435
|
+
resolvedData: this.contextData,
|
|
2436
|
+
});
|
|
2437
|
+
this.contextStore.set(effect.slot, value);
|
|
2438
|
+
}
|
|
2439
|
+
/** The repeater item this element belongs to — the nearest marked row ancestor. */
|
|
2440
|
+
rowItemFor(el) {
|
|
2441
|
+
const row = el?.closest?.('[data-iox-row-idx]');
|
|
2442
|
+
if (!row)
|
|
2443
|
+
return undefined;
|
|
2444
|
+
const source = row.getAttribute('data-iox-row-src') ?? '';
|
|
2445
|
+
const index = Number(row.getAttribute('data-iox-row-idx'));
|
|
2446
|
+
const items = this.contextData?.[source];
|
|
2447
|
+
return Array.isArray(items) && Number.isInteger(index) ? items[index] : undefined;
|
|
2448
|
+
}
|
|
2449
|
+
/** Resolved data for the canvas, kept current by the repeater as it fetches. */
|
|
2450
|
+
setContextData(data) {
|
|
2451
|
+
// MERGE, not replace — a page can hold several repeaters, each publishing its own source.
|
|
2452
|
+
this.contextData = { ...this.contextData, ...(data ?? {}) };
|
|
2453
|
+
}
|
|
2403
2454
|
executeSwiperAction(action) {
|
|
2404
2455
|
// Target is the nodeId of the SliderContainer to control.
|
|
2405
2456
|
const swiper = this.instanceRegistry.get(action.target);
|
|
@@ -4659,6 +4710,23 @@ class BuilderComponent {
|
|
|
4659
4710
|
this.appRef = appRef;
|
|
4660
4711
|
this.moveableCanvas = moveableCanvas;
|
|
4661
4712
|
this.selectoCanvas = selectoCanvas;
|
|
4713
|
+
this.contextStore = inject(ContextStore);
|
|
4714
|
+
/**
|
|
4715
|
+
* Re-apply context-bound inputs on the CANVAS when a slot changes — the builder twin of the
|
|
4716
|
+
* engine's effect. The canvas is a live runtime, so hovering a repeater row while editing must
|
|
4717
|
+
* swap the bound element just as it does on the published site.
|
|
4718
|
+
*
|
|
4719
|
+
* Only nodes that read a slot are touched; nothing else re-renders (trap T2).
|
|
4720
|
+
*/
|
|
4721
|
+
this.contextEffect = effect(() => {
|
|
4722
|
+
const slotValues = this.contextStore.values();
|
|
4723
|
+
if (!Object.keys(slotValues).length)
|
|
4724
|
+
return;
|
|
4725
|
+
const slotKeys = collectContextSlotKeys(this.layout);
|
|
4726
|
+
if (!slotKeys.size)
|
|
4727
|
+
return;
|
|
4728
|
+
this.applyContextBindings(this.layout, withContextSlots({}, slotValues), slotKeys);
|
|
4729
|
+
}, ...(ngDevMode ? [{ debugName: "contextEffect" }] : /* istanbul ignore next */ []));
|
|
4662
4730
|
this.layout = [];
|
|
4663
4731
|
this.dataSources = [];
|
|
4664
4732
|
this.orgId = 'default';
|
|
@@ -4853,6 +4921,33 @@ class BuilderComponent {
|
|
|
4853
4921
|
}
|
|
4854
4922
|
setTimeout(() => this.appRef.tick());
|
|
4855
4923
|
}
|
|
4924
|
+
/**
|
|
4925
|
+
* Walk the tree and push freshly-resolved values into the live component instances of every node
|
|
4926
|
+
* that reads a context slot. Uses the overlay's node→ComponentRef registry, and the shared
|
|
4927
|
+
* `safeSetInput` so an undeclared input can never trigger NG0303.
|
|
4928
|
+
*/
|
|
4929
|
+
applyContextBindings(nodes, data, slotKeys) {
|
|
4930
|
+
for (const node of nodes ?? []) {
|
|
4931
|
+
if (nodeUsesContext(node, slotKeys)) {
|
|
4932
|
+
const ref = this.overlayService.getNodeRef(node)?.componentRef;
|
|
4933
|
+
if (ref) {
|
|
4934
|
+
for (const binding of node.bindings ?? []) {
|
|
4935
|
+
if (!slotKeys.has(binding.source))
|
|
4936
|
+
continue;
|
|
4937
|
+
const sourceData = data[binding.source];
|
|
4938
|
+
if (sourceData == null)
|
|
4939
|
+
continue;
|
|
4940
|
+
const raw = binding.path ? resolvePath(sourceData, binding.path) : sourceData;
|
|
4941
|
+
const value = applyBindingPipes(raw, binding.pipes);
|
|
4942
|
+
if (value !== undefined && value !== null)
|
|
4943
|
+
safeSetInput(ref, binding.prop, value);
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
if (node.children?.length)
|
|
4948
|
+
this.applyContextBindings(node.children, data, slotKeys);
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4856
4951
|
ngOnDestroy() {
|
|
4857
4952
|
this.sub?.unsubscribe();
|
|
4858
4953
|
this.registrySub?.unsubscribe();
|
|
@@ -6192,13 +6287,14 @@ class BuilderRepeaterComponent {
|
|
|
6192
6287
|
this.fetchData();
|
|
6193
6288
|
}
|
|
6194
6289
|
}
|
|
6195
|
-
constructor(dragEngine, overlayService, dataSourceService, dsRegistry, panelEventService, cdr) {
|
|
6290
|
+
constructor(dragEngine, overlayService, dataSourceService, dsRegistry, panelEventService, cdr, interactionEngine) {
|
|
6196
6291
|
this.dragEngine = dragEngine;
|
|
6197
6292
|
this.overlayService = overlayService;
|
|
6198
6293
|
this.dataSourceService = dataSourceService;
|
|
6199
6294
|
this.dsRegistry = dsRegistry;
|
|
6200
6295
|
this.panelEventService = panelEventService;
|
|
6201
6296
|
this.cdr = cdr;
|
|
6297
|
+
this.interactionEngine = interactionEngine;
|
|
6202
6298
|
this._children = [];
|
|
6203
6299
|
this.style = {};
|
|
6204
6300
|
this.nodeId = '';
|
|
@@ -6252,6 +6348,9 @@ class BuilderRepeaterComponent {
|
|
|
6252
6348
|
if (seq !== this._fetchSeq)
|
|
6253
6349
|
return; // a newer fetch superseded this one — ignore stale items
|
|
6254
6350
|
this.items = items.slice(0, this._limit);
|
|
6351
|
+
// Row markers store a REFERENCE (source + index); the engine needs the array to resolve
|
|
6352
|
+
// it back to an item, exactly as the SSR runtime uses resolvedData.
|
|
6353
|
+
this.interactionEngine.setContextData({ [this._source]: this.items });
|
|
6255
6354
|
this.refreshPreviews();
|
|
6256
6355
|
this.isLoading = false;
|
|
6257
6356
|
this.cdr.markForCheck();
|
|
@@ -6306,7 +6405,7 @@ class BuilderRepeaterComponent {
|
|
|
6306
6405
|
if (this.items.length)
|
|
6307
6406
|
setTimeout(() => this.refreshPreviews());
|
|
6308
6407
|
}
|
|
6309
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderRepeaterComponent, deps: [{ token: DragEngineService }, { token: OverlayService }, { token: BuilderDataSourceService }, { token: DataSourceRegistryService }, { token: PanelEventService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6408
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderRepeaterComponent, deps: [{ token: DragEngineService }, { token: OverlayService }, { token: BuilderDataSourceService }, { token: DataSourceRegistryService }, { token: PanelEventService }, { token: i0.ChangeDetectorRef }, { token: InteractionEngineService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6310
6409
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: BuilderRepeaterComponent, isStandalone: false, selector: "app-builder-repeater", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", source: "source", sourcePath: "sourcePath", limit: "limit" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
|
|
6311
6410
|
<ng-template #badgeContent>
|
|
6312
6411
|
<i class="ph-thin ph-repeat"></i>
|
|
@@ -6356,6 +6455,8 @@ class BuilderRepeaterComponent {
|
|
|
6356
6455
|
as siblings of the preview items below. -->
|
|
6357
6456
|
<div *ngFor="let child of children"
|
|
6358
6457
|
[ngClass]="['repeater-item-template', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6458
|
+
[attr.data-iox-row-src]="source || null"
|
|
6459
|
+
[attr.data-iox-row-idx]="source ? 0 : null"
|
|
6359
6460
|
ioxDraggable
|
|
6360
6461
|
[ioxDragData]="child"
|
|
6361
6462
|
[ioxDragSourceId]="dropListId">
|
|
@@ -6368,9 +6469,11 @@ class BuilderRepeaterComponent {
|
|
|
6368
6469
|
</div>
|
|
6369
6470
|
|
|
6370
6471
|
<!-- Preview items — also DIRECT children of repeater-root. -->
|
|
6371
|
-
<ng-container *ngFor="let row of previewRows">
|
|
6472
|
+
<ng-container *ngFor="let row of previewRows; let ri = index">
|
|
6372
6473
|
<div *ngFor="let child of row"
|
|
6373
|
-
[ngClass]="['repeater-preview-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6474
|
+
[ngClass]="['repeater-preview-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6475
|
+
[attr.data-iox-row-src]="source || null"
|
|
6476
|
+
[attr.data-iox-row-idx]="ri + 1">
|
|
6374
6477
|
<ng-container
|
|
6375
6478
|
[ioxRender]="child"
|
|
6376
6479
|
(onClick)="$event"
|
|
@@ -6434,6 +6537,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6434
6537
|
as siblings of the preview items below. -->
|
|
6435
6538
|
<div *ngFor="let child of children"
|
|
6436
6539
|
[ngClass]="['repeater-item-template', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6540
|
+
[attr.data-iox-row-src]="source || null"
|
|
6541
|
+
[attr.data-iox-row-idx]="source ? 0 : null"
|
|
6437
6542
|
ioxDraggable
|
|
6438
6543
|
[ioxDragData]="child"
|
|
6439
6544
|
[ioxDragSourceId]="dropListId">
|
|
@@ -6446,9 +6551,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6446
6551
|
</div>
|
|
6447
6552
|
|
|
6448
6553
|
<!-- Preview items — also DIRECT children of repeater-root. -->
|
|
6449
|
-
<ng-container *ngFor="let row of previewRows">
|
|
6554
|
+
<ng-container *ngFor="let row of previewRows; let ri = index">
|
|
6450
6555
|
<div *ngFor="let child of row"
|
|
6451
|
-
[ngClass]="['repeater-preview-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6556
|
+
[ngClass]="['repeater-preview-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
6557
|
+
[attr.data-iox-row-src]="source || null"
|
|
6558
|
+
[attr.data-iox-row-idx]="ri + 1">
|
|
6452
6559
|
<ng-container
|
|
6453
6560
|
[ioxRender]="child"
|
|
6454
6561
|
(onClick)="$event"
|
|
@@ -6460,7 +6567,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6460
6567
|
</div>
|
|
6461
6568
|
</div>
|
|
6462
6569
|
`, standalone: false, styles: [".repeater-wrapper{position:relative;box-sizing:border-box}.repeater-badge{display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px dashed rgba(203,144,144,.4);border-bottom:none;border-radius:4px 4px 0 0;-webkit-user-select:none;user-select:none}.repeater-wrapper.has-children>.repeater-badge{position:absolute;top:0;left:0;z-index:10;border-radius:0 0 4px;border:1px solid rgba(203,144,144,.5);opacity:0;cursor:pointer;transition:opacity .15s}.repeater-wrapper.has-children:hover>.repeater-badge{opacity:1}.repeater-badge i{font-size:13px}.repeater-badge-empty{opacity:.55}.repeater-badge-count{margin-left:auto;font-weight:600}.repeater-badge-loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.repeater-root{box-sizing:border-box}.repeater-wrapper:not(.has-children) .repeater-root{border:1px dashed rgba(203,144,144,.4);border-top:none;border-radius:0 0 4px 4px;min-height:40px}.repeater-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1.5rem 1rem;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.repeater-placeholder i{font-size:20px}.repeater-item-template{min-width:0}.repeater-preview-child{opacity:.55;pointer-events:none;min-width:0}\n"] }]
|
|
6463
|
-
}], ctorParameters: () => [{ type: DragEngineService }, { type: OverlayService }, { type: BuilderDataSourceService }, { type: DataSourceRegistryService }, { type: PanelEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { children: [{
|
|
6570
|
+
}], ctorParameters: () => [{ type: DragEngineService }, { type: OverlayService }, { type: BuilderDataSourceService }, { type: DataSourceRegistryService }, { type: PanelEventService }, { type: i0.ChangeDetectorRef }, { type: InteractionEngineService }], propDecorators: { children: [{
|
|
6464
6571
|
type: Input
|
|
6465
6572
|
}], style: [{
|
|
6466
6573
|
type: Input
|
|
@@ -7570,6 +7677,19 @@ class InteractionsPanelComponent {
|
|
|
7570
7677
|
this.interactions = [];
|
|
7571
7678
|
/** All canvas nodes available as animation targets. */
|
|
7572
7679
|
this.nodeOptions = [];
|
|
7680
|
+
/** Slot names already used anywhere on the canvas — offered as suggestions, not a closed list.
|
|
7681
|
+
* A slot needs no declaration to work: naming it in a setContext action creates it. */
|
|
7682
|
+
this.knownSlots = [];
|
|
7683
|
+
/** Add-form state for a context action. */
|
|
7684
|
+
this.newSlot = 'currentItem';
|
|
7685
|
+
this.newValueKind = 'item';
|
|
7686
|
+
this.newValueSource = '';
|
|
7687
|
+
this.newValuePath = '';
|
|
7688
|
+
/** Defaults ON — a row that cleared itself on mouseleave would blink on every row-to-row move. */
|
|
7689
|
+
this.newKeepAfterLeave = true;
|
|
7690
|
+
/** Data-source aliases, for the "Pick a value…" mode. */
|
|
7691
|
+
this.sourceOptions = [];
|
|
7692
|
+
this.dsRegistry = inject(DataSourceRegistryService);
|
|
7573
7693
|
/** State for the "add interaction" form */
|
|
7574
7694
|
this.isAdding = false;
|
|
7575
7695
|
this.newTrigger = 'pageLoad';
|
|
@@ -7590,6 +7710,10 @@ class InteractionsPanelComponent {
|
|
|
7590
7710
|
get isSwiperAction() {
|
|
7591
7711
|
return this.newActionType.startsWith('swiper');
|
|
7592
7712
|
}
|
|
7713
|
+
/** The add-form is building a context action — it needs a slot, not a target/duration/easing. */
|
|
7714
|
+
get isNewContextAction() {
|
|
7715
|
+
return isContextAction({ type: this.newActionType });
|
|
7716
|
+
}
|
|
7593
7717
|
ngOnInit() {
|
|
7594
7718
|
this.selectSub = this.overlayService.select$.subscribe(entry => {
|
|
7595
7719
|
this.node = entry?.node ?? null;
|
|
@@ -7598,6 +7722,7 @@ class InteractionsPanelComponent {
|
|
|
7598
7722
|
this.expandedId = null;
|
|
7599
7723
|
this.refreshNodeOptions();
|
|
7600
7724
|
this.refreshDynamicOptions();
|
|
7725
|
+
this.refreshKnownSlots();
|
|
7601
7726
|
});
|
|
7602
7727
|
}
|
|
7603
7728
|
/** Merge component-specific events into trigger options and methods into action options. */
|
|
@@ -7681,9 +7806,22 @@ class InteractionsPanelComponent {
|
|
|
7681
7806
|
confirmAdd() {
|
|
7682
7807
|
if (!this.node)
|
|
7683
7808
|
return;
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7809
|
+
// A context action carries SetContextParams. Without this the add-form produced an action
|
|
7810
|
+
// with no params at all — contextActionEffect would return null and the interaction would
|
|
7811
|
+
// silently do nothing.
|
|
7812
|
+
const params = this.isNewContextAction
|
|
7813
|
+
? {
|
|
7814
|
+
slot: (this.newSlot ?? '').trim(),
|
|
7815
|
+
...(this.newActionType === 'setContext' ? {
|
|
7816
|
+
value: this.newValueKind === 'source'
|
|
7817
|
+
? { kind: 'source', source: this.newValueSource, path: this.newValuePath }
|
|
7818
|
+
: { kind: 'item' },
|
|
7819
|
+
noReverse: this.newKeepAfterLeave,
|
|
7820
|
+
} : {}),
|
|
7821
|
+
}
|
|
7822
|
+
: this.isSwiperGoToSlide
|
|
7823
|
+
? { index: this.newSlideIndex }
|
|
7824
|
+
: undefined;
|
|
7687
7825
|
const action = {
|
|
7688
7826
|
type: this.newActionType,
|
|
7689
7827
|
target: this.newTarget,
|
|
@@ -7704,6 +7842,7 @@ class InteractionsPanelComponent {
|
|
|
7704
7842
|
this.node.interactions.push(interaction);
|
|
7705
7843
|
this.interactions = this.node.interactions;
|
|
7706
7844
|
this.isAdding = false;
|
|
7845
|
+
this.refreshKnownSlots();
|
|
7707
7846
|
}
|
|
7708
7847
|
// ── Remove interaction ───────────────────────────────────
|
|
7709
7848
|
removeInteraction(id) {
|
|
@@ -7733,6 +7872,72 @@ class InteractionsPanelComponent {
|
|
|
7733
7872
|
this.removeInteraction(interaction.id);
|
|
7734
7873
|
}
|
|
7735
7874
|
}
|
|
7875
|
+
// ── Context actions ──────────────────────────────────────
|
|
7876
|
+
/** True for setContext/clearContext — they get a slot editor instead of a target + easing. */
|
|
7877
|
+
isContextAction(action) {
|
|
7878
|
+
return isContextAction(action);
|
|
7879
|
+
}
|
|
7880
|
+
ctxParams(action) {
|
|
7881
|
+
action.params ??= {};
|
|
7882
|
+
return action.params;
|
|
7883
|
+
}
|
|
7884
|
+
slotOf(action) {
|
|
7885
|
+
return this.ctxParams(action).slot ?? '';
|
|
7886
|
+
}
|
|
7887
|
+
setSlot(action, slot) {
|
|
7888
|
+
this.ctxParams(action).slot = (slot ?? '').trim();
|
|
7889
|
+
this.refreshKnownSlots();
|
|
7890
|
+
}
|
|
7891
|
+
valueKindOf(action) {
|
|
7892
|
+
return this.ctxParams(action).value?.kind ?? 'item';
|
|
7893
|
+
}
|
|
7894
|
+
setValueKind(action, kind) {
|
|
7895
|
+
const params = this.ctxParams(action);
|
|
7896
|
+
params.value = kind === 'source'
|
|
7897
|
+
? { kind: 'source', source: this.sourceOptions[0]?.value ?? '', path: '' }
|
|
7898
|
+
: { kind: 'item' };
|
|
7899
|
+
}
|
|
7900
|
+
valueSourceOf(action) {
|
|
7901
|
+
const value = this.ctxParams(action).value;
|
|
7902
|
+
return value?.source ?? '';
|
|
7903
|
+
}
|
|
7904
|
+
setValueSource(action, source) {
|
|
7905
|
+
const params = this.ctxParams(action);
|
|
7906
|
+
params.value = { kind: 'source', source, path: this.valuePathOf(action) };
|
|
7907
|
+
}
|
|
7908
|
+
valuePathOf(action) {
|
|
7909
|
+
const value = this.ctxParams(action).value;
|
|
7910
|
+
return value?.path ?? '';
|
|
7911
|
+
}
|
|
7912
|
+
setValuePath(action, path) {
|
|
7913
|
+
const params = this.ctxParams(action);
|
|
7914
|
+
params.value = { kind: 'source', source: this.valueSourceOf(action), path };
|
|
7915
|
+
}
|
|
7916
|
+
/**
|
|
7917
|
+
* "Keep after leave" — ON means the slot survives mouseleave (stored as `noReverse`).
|
|
7918
|
+
* Defaults to ON: a repeater row that cleared itself on leave would blink on every row-to-row
|
|
7919
|
+
* move. Turning it OFF is the list-container behaviour (leave clears).
|
|
7920
|
+
*/
|
|
7921
|
+
keepAfterLeave(action) {
|
|
7922
|
+
return this.ctxParams(action).noReverse !== false;
|
|
7923
|
+
}
|
|
7924
|
+
setKeepAfterLeave(action, keep) {
|
|
7925
|
+
this.ctxParams(action).noReverse = keep;
|
|
7926
|
+
}
|
|
7927
|
+
refreshKnownSlots() {
|
|
7928
|
+
const slots = new Set();
|
|
7929
|
+
for (const [node] of this.overlayService.getAllNodeEntries()) {
|
|
7930
|
+
for (const ix of node.interactions ?? []) {
|
|
7931
|
+
for (const a of [...(ix.actions ?? []), ...(ix.reverseActions ?? [])]) {
|
|
7932
|
+
const slot = a.params?.slot;
|
|
7933
|
+
if (slot)
|
|
7934
|
+
slots.add(slot);
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
this.knownSlots = [...slots].sort();
|
|
7939
|
+
this.sourceOptions = this.dsRegistry.getAliasOptions?.() ?? [];
|
|
7940
|
+
}
|
|
7736
7941
|
// ── Toggle (reverse actions) ─────────────────────────────
|
|
7737
7942
|
hasToggle(interaction) {
|
|
7738
7943
|
return !!interaction.reverseActions?.length;
|
|
@@ -7768,11 +7973,11 @@ class InteractionsPanelComponent {
|
|
|
7768
7973
|
}
|
|
7769
7974
|
}
|
|
7770
7975
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, deps: [{ token: OverlayService }, { token: ComponentRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7771
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: InteractionsPanelComponent, isStandalone: false, selector: "app-interactions-panel", inputs: { node: "node" }, ngImport: i0, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"], dependencies: [{ 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: i4$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i4$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
|
|
7976
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: InteractionsPanelComponent, isStandalone: false, selector: "app-interactions-panel", inputs: { node: "node" }, ngImport: i0, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <!-- Context actions: slot + what to write. Not an animation, so duration/easing\n below are irrelevant to them. -->\n <ng-container *ngIf=\"isContextAction(action)\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\"\n [ngModel]=\"slotOf(action)\"\n (ngModelChange)=\"setSlot(action, $event)\"\n list=\"ix-slot-options\"\n placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ slotOf(action) || 'slot' }}.field</strong>\n </span>\n </div>\n\n <ng-container *ngIf=\"action.type === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [ngModel]=\"valueKindOf(action)\" (ngModelChange)=\"setValueKind(action, $event)\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\" *ngIf=\"valueKindOf(action) === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [ngModel]=\"valueSourceOf(action)\"\n (ngModelChange)=\"setValueSource(action, $event)\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\"\n [ngModel]=\"valuePathOf(action)\"\n (ngModelChange)=\"setValuePath(action, $event)\"\n placeholder=\"(whole item)\">\n </div>\n </div>\n\n <div class=\"ix-field ix-field-inline\">\n <label>\n <input type=\"checkbox\"\n [ngModel]=\"keepAfterLeave(action)\"\n (ngModelChange)=\"setKeepAfterLeave(action, $event)\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isContextAction(action)\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <!-- Context actions write STATE: they need a slot, not a target/duration/easing. -->\n <ng-container *ngIf=\"isNewContextAction\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\" [(ngModel)]=\"newSlot\" list=\"ix-slot-options-add\" placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options-add\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ newSlot || 'slot' }}.field</strong>\n </span>\n </div>\n <ng-container *ngIf=\"newActionType === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [(ngModel)]=\"newValueKind\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n <div class=\"ix-field-row\" *ngIf=\"newValueKind === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newValueSource\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\" [(ngModel)]=\"newValuePath\" placeholder=\"(whole item)\">\n </div>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newKeepAfterLeave\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isNewContextAction\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction && !isNewContextAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\" *ngIf=\"!isNewContextAction\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\" *ngIf=\"!isNewContextAction\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"], dependencies: [{ 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: i4$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i4$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
|
|
7772
7977
|
}
|
|
7773
7978
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, decorators: [{
|
|
7774
7979
|
type: Component,
|
|
7775
|
-
args: [{ selector: 'app-interactions-panel', standalone: false, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"] }]
|
|
7980
|
+
args: [{ selector: 'app-interactions-panel', standalone: false, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <!-- Context actions: slot + what to write. Not an animation, so duration/easing\n below are irrelevant to them. -->\n <ng-container *ngIf=\"isContextAction(action)\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\"\n [ngModel]=\"slotOf(action)\"\n (ngModelChange)=\"setSlot(action, $event)\"\n list=\"ix-slot-options\"\n placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ slotOf(action) || 'slot' }}.field</strong>\n </span>\n </div>\n\n <ng-container *ngIf=\"action.type === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [ngModel]=\"valueKindOf(action)\" (ngModelChange)=\"setValueKind(action, $event)\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\" *ngIf=\"valueKindOf(action) === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [ngModel]=\"valueSourceOf(action)\"\n (ngModelChange)=\"setValueSource(action, $event)\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\"\n [ngModel]=\"valuePathOf(action)\"\n (ngModelChange)=\"setValuePath(action, $event)\"\n placeholder=\"(whole item)\">\n </div>\n </div>\n\n <div class=\"ix-field ix-field-inline\">\n <label>\n <input type=\"checkbox\"\n [ngModel]=\"keepAfterLeave(action)\"\n (ngModelChange)=\"setKeepAfterLeave(action, $event)\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isContextAction(action)\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <!-- Context actions write STATE: they need a slot, not a target/duration/easing. -->\n <ng-container *ngIf=\"isNewContextAction\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\" [(ngModel)]=\"newSlot\" list=\"ix-slot-options-add\" placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options-add\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ newSlot || 'slot' }}.field</strong>\n </span>\n </div>\n <ng-container *ngIf=\"newActionType === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [(ngModel)]=\"newValueKind\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n <div class=\"ix-field-row\" *ngIf=\"newValueKind === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newValueSource\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\" [(ngModel)]=\"newValuePath\" placeholder=\"(whole item)\">\n </div>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newKeepAfterLeave\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isNewContextAction\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction && !isNewContextAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\" *ngIf=\"!isNewContextAction\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\" *ngIf=\"!isNewContextAction\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"] }]
|
|
7776
7981
|
}], ctorParameters: () => [{ type: OverlayService }, { type: ComponentRegistryService }], propDecorators: { node: [{
|
|
7777
7982
|
type: Input
|
|
7778
7983
|
}] } });
|