@vectoriox/iox-builder 1.4.20 → 1.4.21

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,6 +1088,14 @@ 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
+ /** Action types that animate an element from hidden → visible. */
1094
+ this.ENTRANCE_TYPES = new Set([
1095
+ 'fadeIn', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'scaleIn', 'show',
1096
+ ]);
1097
+ /** Action types that animate an element from visible → hidden. */
1098
+ this.EXIT_TYPES = new Set(['fadeOut', 'scaleOut', 'hide']);
1091
1099
  }
1092
1100
  /** Wire all interactions for a node to its rendered DOM element. */
1093
1101
  attach(node) {
@@ -1098,11 +1106,12 @@ class InteractionEngineService {
1098
1106
  return;
1099
1107
  const cleanups = [];
1100
1108
  for (const ix of node.interactions) {
1101
- // For entrance-type triggers, freeze the target at the animation's
1102
- // starting frame immediately so it is never briefly visible before
1103
- // the trigger fires (avoids the flash-then-animate flicker).
1104
- if (ix.trigger === 'pageLoad' || ix.trigger === 'viewportEnter') {
1105
- for (const action of ix.actions) {
1109
+ // Apply pre-state to targets of ANY entrance animation so the element
1110
+ // starts hidden regardless of whether the trigger is automatic (pageLoad,
1111
+ // viewportEnter) or user-driven (click). Without this, a click-triggered
1112
+ // fadeIn on a menu overlay would start the overlay fully visible.
1113
+ for (const action of ix.actions) {
1114
+ if (this.ENTRANCE_TYPES.has(action.type)) {
1106
1115
  const target = this.resolveTarget(node, action);
1107
1116
  if (target)
1108
1117
  this.applyPreState(target, action);
@@ -1127,9 +1136,20 @@ class InteractionEngineService {
1127
1136
  const ref = this.overlayService.getNodeRef(node);
1128
1137
  if (ref) {
1129
1138
  ref.element.getAnimations().forEach(a => a.cancel());
1130
- ref.element.style.removeProperty('opacity');
1131
- ref.element.style.removeProperty('transform');
1132
- ref.element.style.removeProperty('visibility');
1139
+ this.clearInlineAnimationStyles(ref.element);
1140
+ }
1141
+ // Also clean up pre-state on any target elements this node was animating.
1142
+ if (node.interactions) {
1143
+ for (const ix of node.interactions) {
1144
+ for (const action of ix.actions) {
1145
+ const target = this.resolveTarget(node, action);
1146
+ if (target && this.preStatedElements.has(target)) {
1147
+ target.getAnimations().forEach(a => a.cancel());
1148
+ this.clearInlineAnimationStyles(target);
1149
+ this.preStatedElements.delete(target);
1150
+ }
1151
+ }
1152
+ }
1133
1153
  }
1134
1154
  }
1135
1155
  /** Re-attach interactions after they have been edited in the panel. */
@@ -1249,23 +1269,35 @@ class InteractionEngineService {
1249
1269
  if (!keyframes.length)
1250
1270
  return;
1251
1271
  // 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
1252
- // This eliminates any flicker during the delay period.
1253
- element.animate(keyframes, {
1272
+ const anim = element.animate(keyframes, {
1254
1273
  duration: action.duration,
1255
1274
  delay: action.delay,
1256
1275
  easing: action.easing,
1257
1276
  fill: 'both',
1258
1277
  });
1278
+ if (this.ENTRANCE_TYPES.has(action.type)) {
1279
+ // Once the entrance animation finishes the element is fully visible —
1280
+ // restore pointer events so it can be interacted with.
1281
+ anim.finished.then(() => {
1282
+ element.style.removeProperty('pointer-events');
1283
+ }).catch(() => { });
1284
+ }
1285
+ else if (this.EXIT_TYPES.has(action.type)) {
1286
+ // Once the exit animation finishes the element is invisible —
1287
+ // disable pointer events so it doesn't silently block clicks underneath it.
1288
+ anim.finished.then(() => {
1289
+ element.style.setProperty('pointer-events', 'none');
1290
+ }).catch(() => { });
1291
+ }
1259
1292
  }
1260
1293
  /**
1261
1294
  * Freeze an element at the animation's starting frame immediately, before any
1262
- * trigger fires. Without this, entrance animations (fadeIn, moveUp, …) show the
1263
- * element at its natural visible state for the time between mount and trigger —
1264
- * causing a visible-then-disappear-then-animate flicker.
1295
+ * trigger fires. Called for ALL entrance-type actions regardless of trigger so
1296
+ * that a click-triggered fadeIn on a menu overlay also starts it hidden.
1265
1297
  *
1266
- * Only call this for triggers that will eventually animate the element in
1267
- * (pageLoad, viewportEnter). Click/hover triggers must NOT pre-hide the element
1268
- * because the element should remain normally visible until interacted with.
1298
+ * Also sets pointer-events:none so the invisible element does not block clicks
1299
+ * on other content beneath it. Pointer events are restored in executeAction()
1300
+ * once the entrance animation finishes.
1269
1301
  */
1270
1302
  applyPreState(element, action) {
1271
1303
  const { keyframes } = this.buildAnimation(action);
@@ -1275,6 +1307,18 @@ class InteractionEngineService {
1275
1307
  for (const [prop, val] of Object.entries(first)) {
1276
1308
  element.style[prop] = String(val);
1277
1309
  }
1310
+ // Invisible element must not intercept pointer events.
1311
+ const opacity = first['opacity'];
1312
+ if (opacity === '0' || opacity === 0) {
1313
+ element.style.setProperty('pointer-events', 'none');
1314
+ }
1315
+ this.preStatedElements.add(element);
1316
+ }
1317
+ clearInlineAnimationStyles(element) {
1318
+ element.style.removeProperty('opacity');
1319
+ element.style.removeProperty('transform');
1320
+ element.style.removeProperty('visibility');
1321
+ element.style.removeProperty('pointer-events');
1278
1322
  }
1279
1323
  reverseAction(element, action) {
1280
1324
  const { keyframes } = this.buildAnimation(action);