@vectoriox/iox-builder 1.4.20 → 1.4.22
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.
|
@@ -1088,24 +1088,57 @@ class InteractionEngineService {
|
|
|
1088
1088
|
constructor(overlayService) {
|
|
1089
1089
|
this.overlayService = overlayService;
|
|
1090
1090
|
this.attached = new Map();
|
|
1091
|
+
/** Elements that have had pre-state applied (hidden by attach). */
|
|
1092
|
+
this.preStatedElements = new Set();
|
|
1093
|
+
/**
|
|
1094
|
+
* Pre-states queued for elements that weren't registered yet when the owner
|
|
1095
|
+
* node's attach() ran. Keyed by target node ID. Consumed when the target
|
|
1096
|
+
* element's own attach() fires.
|
|
1097
|
+
*/
|
|
1098
|
+
this.pendingPreStates = new Map();
|
|
1099
|
+
/** Action types that animate an element from hidden → visible. */
|
|
1100
|
+
this.ENTRANCE_TYPES = new Set([
|
|
1101
|
+
'fadeIn', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'scaleIn', 'show',
|
|
1102
|
+
]);
|
|
1103
|
+
/** Action types that animate an element from visible → hidden. */
|
|
1104
|
+
this.EXIT_TYPES = new Set(['fadeOut', 'scaleOut', 'hide']);
|
|
1091
1105
|
}
|
|
1092
1106
|
/** Wire all interactions for a node to its rendered DOM element. */
|
|
1093
1107
|
attach(node) {
|
|
1094
|
-
if (!node.interactions?.length)
|
|
1095
|
-
return;
|
|
1096
1108
|
const ref = this.overlayService.getNodeRef(node);
|
|
1097
1109
|
if (!ref)
|
|
1098
1110
|
return;
|
|
1111
|
+
// Apply any pre-states that were queued by other nodes that target THIS
|
|
1112
|
+
// element with an entrance animation but registered before we did.
|
|
1113
|
+
if (node.id) {
|
|
1114
|
+
const pending = this.pendingPreStates.get(node.id);
|
|
1115
|
+
if (pending) {
|
|
1116
|
+
for (const action of pending) {
|
|
1117
|
+
this.applyPreState(ref.element, action);
|
|
1118
|
+
}
|
|
1119
|
+
this.pendingPreStates.delete(node.id);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (!node.interactions?.length)
|
|
1123
|
+
return;
|
|
1099
1124
|
const cleanups = [];
|
|
1100
1125
|
for (const ix of node.interactions) {
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
|
|
1105
|
-
|
|
1126
|
+
// Apply pre-state to targets of ANY entrance animation so the element
|
|
1127
|
+
// starts hidden regardless of whether the trigger is automatic (pageLoad,
|
|
1128
|
+
// viewportEnter) or user-driven (click).
|
|
1129
|
+
for (const action of ix.actions) {
|
|
1130
|
+
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
1106
1131
|
const target = this.resolveTarget(node, action);
|
|
1107
|
-
if (target)
|
|
1132
|
+
if (target) {
|
|
1108
1133
|
this.applyPreState(target, action);
|
|
1134
|
+
}
|
|
1135
|
+
else if (action.target && action.target !== 'self') {
|
|
1136
|
+
// Target element not registered yet — queue the pre-state so
|
|
1137
|
+
// it gets applied the moment that element's attach() fires.
|
|
1138
|
+
const list = this.pendingPreStates.get(action.target) ?? [];
|
|
1139
|
+
list.push(action);
|
|
1140
|
+
this.pendingPreStates.set(action.target, list);
|
|
1141
|
+
}
|
|
1109
1142
|
}
|
|
1110
1143
|
}
|
|
1111
1144
|
const cleanup = this.attachInteraction(node, ix, ref.element);
|
|
@@ -1127,9 +1160,34 @@ class InteractionEngineService {
|
|
|
1127
1160
|
const ref = this.overlayService.getNodeRef(node);
|
|
1128
1161
|
if (ref) {
|
|
1129
1162
|
ref.element.getAnimations().forEach(a => a.cancel());
|
|
1130
|
-
ref.element
|
|
1131
|
-
|
|
1132
|
-
|
|
1163
|
+
this.clearInlineAnimationStyles(ref.element);
|
|
1164
|
+
}
|
|
1165
|
+
// Also clean up pre-state on any target elements this node was animating.
|
|
1166
|
+
if (node.interactions) {
|
|
1167
|
+
for (const ix of node.interactions) {
|
|
1168
|
+
for (const action of ix.actions) {
|
|
1169
|
+
// Remove any pending pre-states for unregistered targets.
|
|
1170
|
+
if (action.target && action.target !== 'self') {
|
|
1171
|
+
const pending = this.pendingPreStates.get(action.target);
|
|
1172
|
+
if (pending) {
|
|
1173
|
+
const filtered = pending.filter(a => a !== action);
|
|
1174
|
+
if (filtered.length) {
|
|
1175
|
+
this.pendingPreStates.set(action.target, filtered);
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
this.pendingPreStates.delete(action.target);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
// Clean up inline styles on already-registered target elements.
|
|
1183
|
+
const target = this.resolveTarget(node, action);
|
|
1184
|
+
if (target && this.preStatedElements.has(target)) {
|
|
1185
|
+
target.getAnimations().forEach(a => a.cancel());
|
|
1186
|
+
this.clearInlineAnimationStyles(target);
|
|
1187
|
+
this.preStatedElements.delete(target);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1133
1191
|
}
|
|
1134
1192
|
}
|
|
1135
1193
|
/** Re-attach interactions after they have been edited in the panel. */
|
|
@@ -1249,23 +1307,35 @@ class InteractionEngineService {
|
|
|
1249
1307
|
if (!keyframes.length)
|
|
1250
1308
|
return;
|
|
1251
1309
|
// 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
|
|
1252
|
-
|
|
1253
|
-
element.animate(keyframes, {
|
|
1310
|
+
const anim = element.animate(keyframes, {
|
|
1254
1311
|
duration: action.duration,
|
|
1255
1312
|
delay: action.delay,
|
|
1256
1313
|
easing: action.easing,
|
|
1257
1314
|
fill: 'both',
|
|
1258
1315
|
});
|
|
1316
|
+
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
1317
|
+
// Once the entrance animation finishes the element is fully visible —
|
|
1318
|
+
// restore pointer events so it can be interacted with.
|
|
1319
|
+
anim.finished.then(() => {
|
|
1320
|
+
element.style.removeProperty('pointer-events');
|
|
1321
|
+
}).catch(() => { });
|
|
1322
|
+
}
|
|
1323
|
+
else if (this.EXIT_TYPES.has(action.type)) {
|
|
1324
|
+
// Once the exit animation finishes the element is invisible —
|
|
1325
|
+
// disable pointer events so it doesn't silently block clicks underneath it.
|
|
1326
|
+
anim.finished.then(() => {
|
|
1327
|
+
element.style.setProperty('pointer-events', 'none');
|
|
1328
|
+
}).catch(() => { });
|
|
1329
|
+
}
|
|
1259
1330
|
}
|
|
1260
1331
|
/**
|
|
1261
1332
|
* Freeze an element at the animation's starting frame immediately, before any
|
|
1262
|
-
* trigger fires.
|
|
1263
|
-
*
|
|
1264
|
-
* causing a visible-then-disappear-then-animate flicker.
|
|
1333
|
+
* trigger fires. Called for ALL entrance-type actions regardless of trigger so
|
|
1334
|
+
* that a click-triggered fadeIn on a menu overlay also starts it hidden.
|
|
1265
1335
|
*
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1336
|
+
* Also sets pointer-events:none so the invisible element does not block clicks
|
|
1337
|
+
* on other content beneath it. Pointer events are restored in executeAction()
|
|
1338
|
+
* once the entrance animation finishes.
|
|
1269
1339
|
*/
|
|
1270
1340
|
applyPreState(element, action) {
|
|
1271
1341
|
const { keyframes } = this.buildAnimation(action);
|
|
@@ -1275,6 +1345,18 @@ class InteractionEngineService {
|
|
|
1275
1345
|
for (const [prop, val] of Object.entries(first)) {
|
|
1276
1346
|
element.style[prop] = String(val);
|
|
1277
1347
|
}
|
|
1348
|
+
// Invisible element must not intercept pointer events.
|
|
1349
|
+
const opacity = first['opacity'];
|
|
1350
|
+
if (opacity === '0' || opacity === 0) {
|
|
1351
|
+
element.style.setProperty('pointer-events', 'none');
|
|
1352
|
+
}
|
|
1353
|
+
this.preStatedElements.add(element);
|
|
1354
|
+
}
|
|
1355
|
+
clearInlineAnimationStyles(element) {
|
|
1356
|
+
element.style.removeProperty('opacity');
|
|
1357
|
+
element.style.removeProperty('transform');
|
|
1358
|
+
element.style.removeProperty('visibility');
|
|
1359
|
+
element.style.removeProperty('pointer-events');
|
|
1278
1360
|
}
|
|
1279
1361
|
reverseAction(element, action) {
|
|
1280
1362
|
const { keyframes } = this.buildAnimation(action);
|