rampkit-expo-dev 0.0.42 → 0.0.44

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.
@@ -2,7 +2,7 @@ import { RampKitContext } from "./types";
2
2
  export declare const injectedHardening = "\n(function(){\n try {\n var meta = document.querySelector('meta[name=\"viewport\"]');\n if (!meta) { meta = document.createElement('meta'); meta.name = 'viewport'; document.head.appendChild(meta); }\n meta.setAttribute('content','width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover');\n var style = document.createElement('style');\n style.textContent='html,body{overflow-x:hidden!important;} html,body,*{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important;-ms-user-select:none!important;touch-action: pan-y;} *{-webkit-tap-highlight-color: rgba(0,0,0,0)!important;} ::selection{background: transparent!important;} ::-moz-selection{background: transparent!important;} a,img{-webkit-user-drag:none!important;user-drag:none!important;-webkit-touch-callout:none!important} input,textarea{caret-color:transparent!important;-webkit-user-select:none!important;user-select:none!important}';\n document.head.appendChild(style);\n var prevent=function(e){e.preventDefault&&e.preventDefault();};\n document.addEventListener('gesturestart',prevent,{passive:false});\n document.addEventListener('gesturechange',prevent,{passive:false});\n document.addEventListener('gestureend',prevent,{passive:false});\n document.addEventListener('dblclick',prevent,{passive:false});\n document.addEventListener('wheel',function(e){ if(e.ctrlKey) e.preventDefault(); },{passive:false});\n document.addEventListener('touchmove',function(e){ if(e.scale && e.scale !== 1) e.preventDefault(); },{passive:false});\n document.addEventListener('selectstart',prevent,{passive:false,capture:true});\n document.addEventListener('contextmenu',prevent,{passive:false,capture:true});\n document.addEventListener('copy',prevent,{passive:false,capture:true});\n document.addEventListener('cut',prevent,{passive:false,capture:true});\n document.addEventListener('paste',prevent,{passive:false,capture:true});\n document.addEventListener('dragstart',prevent,{passive:false,capture:true});\n // Belt-and-suspenders: aggressively clear any attempted selection\n var clearSel=function(){\n try{var sel=window.getSelection&&window.getSelection(); if(sel&&sel.removeAllRanges) sel.removeAllRanges();}catch(_){} }\n document.addEventListener('selectionchange',clearSel,{passive:true,capture:true});\n document.onselectstart=function(){ clearSel(); return false; };\n try{ document.documentElement.style.webkitUserSelect='none'; document.documentElement.style.userSelect='none'; }catch(_){ }\n try{ document.body.style.webkitUserSelect='none'; document.body.style.userSelect='none'; }catch(_){ }\n var __selTimer = setInterval(clearSel, 160);\n window.addEventListener('pagehide',function(){ try{ clearInterval(__selTimer); }catch(_){} });\n // Continuously enforce no-select on all elements and new nodes\n var enforceNoSelect = function(el){\n try{\n el.style && (el.style.webkitUserSelect='none', el.style.userSelect='none', el.style.webkitTouchCallout='none');\n el.setAttribute && (el.setAttribute('unselectable','on'), el.setAttribute('contenteditable','false'));\n }catch(_){}\n }\n try{\n var all=document.getElementsByTagName('*');\n for(var i=0;i<all.length;i++){ enforceNoSelect(all[i]); }\n var obs = new MutationObserver(function(muts){\n for(var j=0;j<muts.length;j++){\n var m=muts[j];\n if(m.type==='childList'){\n m.addedNodes && m.addedNodes.forEach && m.addedNodes.forEach(function(n){ if(n && n.nodeType===1){ enforceNoSelect(n); var q=n.getElementsByTagName? n.getElementsByTagName('*'): []; for(var k=0;k<q.length;k++){ enforceNoSelect(q[k]); }}});\n } else if(m.type==='attributes'){\n enforceNoSelect(m.target);\n }\n }\n });\n obs.observe(document.documentElement,{ childList:true, subtree:true, attributes:true, attributeFilter:['contenteditable','style'] });\n }catch(_){ }\n } catch(_) {}\n})(); true;\n";
3
3
  export declare const injectedNoSelect = "\n(function(){\n try {\n if (window.__rkNoSelectApplied) return true;\n window.__rkNoSelectApplied = true;\n var style = document.getElementById('rk-no-select-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'rk-no-select-style';\n style.innerHTML = \"\n * {\n user-select: none !important;\n -webkit-user-select: none !important;\n -webkit-touch-callout: none !important;\n }\n ::selection {\n background: transparent !important;\n }\n \";\n document.head.appendChild(style);\n }\n var prevent = function(e){ if(e && e.preventDefault) e.preventDefault(); return false; };\n document.addEventListener('contextmenu', prevent, { passive: false, capture: true });\n document.addEventListener('selectstart', prevent, { passive: false, capture: true });\n } catch (_) {}\n true;\n})();\n";
4
4
  export declare const injectedVarsHandler = "\n(function(){\n try {\n if (window.__rkVarsHandlerApplied) return true;\n window.__rkVarsHandlerApplied = true;\n \n // Handler function that updates variables and notifies the page\n window.__rkHandleVarsUpdate = function(vars) {\n if (!vars || typeof vars !== 'object') return;\n // Update the global variables object\n window.__rampkitVariables = vars;\n // Dispatch a custom event that the page's JS can listen to for re-rendering\n try {\n document.dispatchEvent(new CustomEvent('rampkit:vars-updated', { detail: vars }));\n } catch(e) {}\n // Also try calling a global handler if the page defined one\n try {\n if (typeof window.onRampkitVarsUpdate === 'function') {\n window.onRampkitVarsUpdate(vars);\n }\n } catch(e) {}\n };\n \n // Listen for message events from React Native\n document.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n \n // Also listen on window for compatibility\n window.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n } catch (_) {}\n true;\n})();\n";
5
- export declare const injectedButtonAnimations = "\n(function(){\n try {\n if (window.__rkButtonAnimApplied) return true;\n window.__rkButtonAnimApplied = true;\n \n // Add styles for button animations\n var style = document.createElement('style');\n style.id = 'rk-button-anim-style';\n style.textContent = \n '*.rk-pressed, .rk-pressed, [class*=\"rk-pressed\"] {' +\n ' transform: scale(0.97) !important;' +\n ' -webkit-transform: scale(0.97) !important;' +\n ' opacity: 0.8 !important;' +\n ' transform-origin: center center !important;' +\n ' -webkit-transform-origin: center center !important;' +\n ' transition: transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1), -webkit-transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1) !important;' +\n ' -webkit-transition: -webkit-transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1) !important;' +\n '}' +\n '*.rk-released, .rk-released, [class*=\"rk-released\"] {' +\n ' transform: scale(1) !important;' +\n ' -webkit-transform: scale(1) !important;' +\n ' opacity: 1 !important;' +\n ' transform-origin: center center !important;' +\n ' -webkit-transform-origin: center center !important;' +\n ' transition: transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1), -webkit-transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;' +\n ' -webkit-transition: -webkit-transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;' +\n '}';\n document.head.appendChild(style);\n \n // Find any interactive element in the parent chain\n function findInteractive(el) {\n var current = el;\n for (var i = 0; i < 15 && current; i++) {\n if (!current.tagName) { current = current.parentElement; continue; }\n var tag = current.tagName.toLowerCase();\n // Match common interactive elements\n if (tag === 'button' || tag === 'a' || tag === 'input' || tag === 'select') return current;\n // Match elements with click handlers or data attributes\n if (current.onclick || current.hasAttribute('onclick')) return current;\n if (current.hasAttribute('data-rampkit-action')) return current;\n if (current.hasAttribute('data-rampkit-navigate')) return current;\n if (current.hasAttribute('data-rampkit-tap')) return current;\n // Match elements with role=\"button\" or tabindex\n if (current.getAttribute('role') === 'button') return current;\n if (current.hasAttribute('tabindex')) return current;\n // Match common button classes\n if (current.className && typeof current.className === 'string') {\n var cls = current.className.toLowerCase();\n if (cls.indexOf('btn') !== -1 || cls.indexOf('button') !== -1 || cls.indexOf('cta') !== -1 || cls.indexOf('interactive') !== -1) return current;\n }\n // Match elements with cursor pointer style\n try {\n var computed = window.getComputedStyle(current);\n if (computed && computed.cursor === 'pointer') return current;\n } catch(e) {}\n current = current.parentElement;\n }\n return null;\n }\n \n var pressed = null;\n var releaseTimer = null;\n \n function onStart(e) {\n try {\n var target = findInteractive(e.target);\n if (!target) return;\n if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }\n target.classList.remove('rk-released');\n target.classList.add('rk-pressed');\n pressed = target;\n } catch(err) { console.log('[RK] touch error:', err); }\n }\n \n function onEnd(e) {\n try {\n if (!pressed) return;\n var t = pressed;\n t.classList.remove('rk-pressed');\n t.classList.add('rk-released');\n releaseTimer = setTimeout(function() {\n t.classList.remove('rk-released');\n releaseTimer = null;\n }, 300);\n pressed = null;\n } catch(err) {}\n }\n \n function onCancel(e) {\n try {\n if (!pressed) return;\n pressed.classList.remove('rk-pressed', 'rk-released');\n pressed = null;\n if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }\n } catch(err) {}\n }\n \n // Capture phase for immediate response\n document.addEventListener('touchstart', onStart, { passive: true, capture: true });\n document.addEventListener('touchend', onEnd, { passive: true, capture: true });\n document.addEventListener('touchcancel', onCancel, { passive: true, capture: true });\n document.addEventListener('mousedown', onStart, { passive: true, capture: true });\n document.addEventListener('mouseup', onEnd, { passive: true, capture: true });\n \n console.log('[RK] Button animations initialized');\n } catch (err) { console.log('[RK] Button anim error:', err); }\n true;\n})();\n";
5
+ export declare const injectedButtonAnimations = "\n(function(){\n try {\n if (window.__rkButtonAnimApplied) return true;\n window.__rkButtonAnimApplied = true;\n \n var pressed = null;\n var pressedOriginalTransform = '';\n var pressedOriginalOpacity = '';\n var pressedOriginalTransition = '';\n var releaseTimer = null;\n \n // Find interactive element - very permissive, looks for any clickable-looking element\n function findInteractive(el) {\n var current = el;\n for (var i = 0; i < 20 && current && current !== document.body && current !== document.documentElement; i++) {\n if (!current || !current.tagName) { current = current.parentElement; continue; }\n var tag = current.tagName.toLowerCase();\n \n // Skip tiny elements (likely icons inside buttons)\n var rect = current.getBoundingClientRect();\n if (rect.width < 20 || rect.height < 20) { current = current.parentElement; continue; }\n \n // Match standard interactive elements\n if (tag === 'button' || tag === 'a' || tag === 'input' || tag === 'select') return current;\n \n // Match elements with any data attribute containing action/navigate/tap/click\n var attrs = current.attributes;\n if (attrs) {\n for (var j = 0; j < attrs.length; j++) {\n var attrName = attrs[j].name.toLowerCase();\n if (attrName.indexOf('click') !== -1 || attrName.indexOf('tap') !== -1 || \n attrName.indexOf('action') !== -1 || attrName.indexOf('navigate') !== -1 ||\n attrName.indexOf('press') !== -1) {\n return current;\n }\n }\n }\n \n // Match elements with onclick\n if (current.onclick || current.hasAttribute('onclick')) return current;\n \n // Match elements with role=\"button\" or tabindex\n if (current.getAttribute('role') === 'button') return current;\n \n // Match any element with an ID containing button/btn/cta\n var id = current.id || '';\n if (id && (id.toLowerCase().indexOf('button') !== -1 || id.toLowerCase().indexOf('btn') !== -1 || id.toLowerCase().indexOf('cta') !== -1)) return current;\n \n // Match elements with button-like classes\n var className = current.className;\n if (className && typeof className === 'string') {\n var cls = className.toLowerCase();\n if (cls.indexOf('btn') !== -1 || cls.indexOf('button') !== -1 || cls.indexOf('cta') !== -1 || \n cls.indexOf('clickable') !== -1 || cls.indexOf('tappable') !== -1 || cls.indexOf('pressable') !== -1) {\n return current;\n }\n }\n \n // Match elements with cursor pointer\n try {\n var computed = window.getComputedStyle(current);\n if (computed && computed.cursor === 'pointer') return current;\n } catch(e) {}\n \n current = current.parentElement;\n }\n return null;\n }\n \n function applyPressedStyle(el) {\n if (!el || !el.style) return;\n // Save original styles\n pressedOriginalTransform = el.style.transform || '';\n pressedOriginalOpacity = el.style.opacity || '';\n pressedOriginalTransition = el.style.transition || '';\n // Apply pressed style with inline styles for maximum specificity\n el.style.transition = 'transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1)';\n el.style.transform = 'scale(0.97)';\n el.style.opacity = '0.8';\n }\n \n function applyReleasedStyle(el) {\n if (!el || !el.style) return;\n // Apply spring-back animation\n el.style.transition = 'transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1)';\n el.style.transform = pressedOriginalTransform || 'scale(1)';\n el.style.opacity = pressedOriginalOpacity || '1';\n }\n \n function resetStyle(el) {\n if (!el || !el.style) return;\n el.style.transform = pressedOriginalTransform;\n el.style.opacity = pressedOriginalOpacity;\n el.style.transition = pressedOriginalTransition;\n }\n \n function onTouchStart(e) {\n try {\n var target = findInteractive(e.target);\n if (!target) return;\n if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }\n if (pressed && pressed !== target) { resetStyle(pressed); }\n applyPressedStyle(target);\n pressed = target;\n } catch(err) {}\n }\n \n function onTouchEnd(e) {\n try {\n if (!pressed) return;\n var t = pressed;\n applyReleasedStyle(t);\n releaseTimer = setTimeout(function() {\n resetStyle(t);\n releaseTimer = null;\n }, 300);\n pressed = null;\n } catch(err) {}\n }\n \n function onTouchCancel(e) {\n try {\n if (!pressed) return;\n resetStyle(pressed);\n pressed = null;\n if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }\n } catch(err) {}\n }\n \n // Use capture phase for immediate response before any other handlers\n document.addEventListener('touchstart', onTouchStart, { passive: true, capture: true });\n document.addEventListener('touchend', onTouchEnd, { passive: true, capture: true });\n document.addEventListener('touchcancel', onTouchCancel, { passive: true, capture: true });\n // Mouse events for testing\n document.addEventListener('mousedown', onTouchStart, { passive: true, capture: true });\n document.addEventListener('mouseup', onTouchEnd, { passive: true, capture: true });\n \n } catch (err) {}\n true;\n})();\n";
6
6
  export type ScreenPayload = {
7
7
  id: string;
8
8
  html: string;
@@ -175,113 +175,146 @@ exports.injectedVarsHandler = `
175
175
  `;
176
176
  // Button tap animation script - handles spring animations for interactive elements
177
177
  // Triggers on touchstart (not click) for immediate feedback
178
+ // Uses inline styles for maximum compatibility
178
179
  exports.injectedButtonAnimations = `
179
180
  (function(){
180
181
  try {
181
182
  if (window.__rkButtonAnimApplied) return true;
182
183
  window.__rkButtonAnimApplied = true;
183
184
 
184
- // Add styles for button animations
185
- var style = document.createElement('style');
186
- style.id = 'rk-button-anim-style';
187
- style.textContent =
188
- '*.rk-pressed, .rk-pressed, [class*="rk-pressed"] {' +
189
- ' transform: scale(0.97) !important;' +
190
- ' -webkit-transform: scale(0.97) !important;' +
191
- ' opacity: 0.8 !important;' +
192
- ' transform-origin: center center !important;' +
193
- ' -webkit-transform-origin: center center !important;' +
194
- ' transition: transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1), -webkit-transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1) !important;' +
195
- ' -webkit-transition: -webkit-transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1) !important;' +
196
- '}' +
197
- '*.rk-released, .rk-released, [class*="rk-released"] {' +
198
- ' transform: scale(1) !important;' +
199
- ' -webkit-transform: scale(1) !important;' +
200
- ' opacity: 1 !important;' +
201
- ' transform-origin: center center !important;' +
202
- ' -webkit-transform-origin: center center !important;' +
203
- ' transition: transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1), -webkit-transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;' +
204
- ' -webkit-transition: -webkit-transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;' +
205
- '}';
206
- document.head.appendChild(style);
185
+ var pressed = null;
186
+ var pressedOriginalTransform = '';
187
+ var pressedOriginalOpacity = '';
188
+ var pressedOriginalTransition = '';
189
+ var releaseTimer = null;
207
190
 
208
- // Find any interactive element in the parent chain
191
+ // Find interactive element - very permissive, looks for any clickable-looking element
209
192
  function findInteractive(el) {
210
193
  var current = el;
211
- for (var i = 0; i < 15 && current; i++) {
212
- if (!current.tagName) { current = current.parentElement; continue; }
194
+ for (var i = 0; i < 20 && current && current !== document.body && current !== document.documentElement; i++) {
195
+ if (!current || !current.tagName) { current = current.parentElement; continue; }
213
196
  var tag = current.tagName.toLowerCase();
214
- // Match common interactive elements
197
+
198
+ // Skip tiny elements (likely icons inside buttons)
199
+ var rect = current.getBoundingClientRect();
200
+ if (rect.width < 20 || rect.height < 20) { current = current.parentElement; continue; }
201
+
202
+ // Match standard interactive elements
215
203
  if (tag === 'button' || tag === 'a' || tag === 'input' || tag === 'select') return current;
216
- // Match elements with click handlers or data attributes
204
+
205
+ // Match elements with any data attribute containing action/navigate/tap/click
206
+ var attrs = current.attributes;
207
+ if (attrs) {
208
+ for (var j = 0; j < attrs.length; j++) {
209
+ var attrName = attrs[j].name.toLowerCase();
210
+ if (attrName.indexOf('click') !== -1 || attrName.indexOf('tap') !== -1 ||
211
+ attrName.indexOf('action') !== -1 || attrName.indexOf('navigate') !== -1 ||
212
+ attrName.indexOf('press') !== -1) {
213
+ return current;
214
+ }
215
+ }
216
+ }
217
+
218
+ // Match elements with onclick
217
219
  if (current.onclick || current.hasAttribute('onclick')) return current;
218
- if (current.hasAttribute('data-rampkit-action')) return current;
219
- if (current.hasAttribute('data-rampkit-navigate')) return current;
220
- if (current.hasAttribute('data-rampkit-tap')) return current;
220
+
221
221
  // Match elements with role="button" or tabindex
222
222
  if (current.getAttribute('role') === 'button') return current;
223
- if (current.hasAttribute('tabindex')) return current;
224
- // Match common button classes
225
- if (current.className && typeof current.className === 'string') {
226
- var cls = current.className.toLowerCase();
227
- if (cls.indexOf('btn') !== -1 || cls.indexOf('button') !== -1 || cls.indexOf('cta') !== -1 || cls.indexOf('interactive') !== -1) return current;
223
+
224
+ // Match any element with an ID containing button/btn/cta
225
+ var id = current.id || '';
226
+ if (id && (id.toLowerCase().indexOf('button') !== -1 || id.toLowerCase().indexOf('btn') !== -1 || id.toLowerCase().indexOf('cta') !== -1)) return current;
227
+
228
+ // Match elements with button-like classes
229
+ var className = current.className;
230
+ if (className && typeof className === 'string') {
231
+ var cls = className.toLowerCase();
232
+ if (cls.indexOf('btn') !== -1 || cls.indexOf('button') !== -1 || cls.indexOf('cta') !== -1 ||
233
+ cls.indexOf('clickable') !== -1 || cls.indexOf('tappable') !== -1 || cls.indexOf('pressable') !== -1) {
234
+ return current;
235
+ }
228
236
  }
229
- // Match elements with cursor pointer style
237
+
238
+ // Match elements with cursor pointer
230
239
  try {
231
240
  var computed = window.getComputedStyle(current);
232
241
  if (computed && computed.cursor === 'pointer') return current;
233
242
  } catch(e) {}
243
+
234
244
  current = current.parentElement;
235
245
  }
236
246
  return null;
237
247
  }
238
248
 
239
- var pressed = null;
240
- var releaseTimer = null;
249
+ function applyPressedStyle(el) {
250
+ if (!el || !el.style) return;
251
+ // Save original styles
252
+ pressedOriginalTransform = el.style.transform || '';
253
+ pressedOriginalOpacity = el.style.opacity || '';
254
+ pressedOriginalTransition = el.style.transition || '';
255
+ // Apply pressed style with inline styles for maximum specificity
256
+ el.style.transition = 'transform 80ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity 80ms cubic-bezier(0.25, 0.1, 0.25, 1)';
257
+ el.style.transform = 'scale(0.97)';
258
+ el.style.opacity = '0.8';
259
+ }
260
+
261
+ function applyReleasedStyle(el) {
262
+ if (!el || !el.style) return;
263
+ // Apply spring-back animation
264
+ el.style.transition = 'transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 280ms cubic-bezier(0.34, 1.56, 0.64, 1)';
265
+ el.style.transform = pressedOriginalTransform || 'scale(1)';
266
+ el.style.opacity = pressedOriginalOpacity || '1';
267
+ }
268
+
269
+ function resetStyle(el) {
270
+ if (!el || !el.style) return;
271
+ el.style.transform = pressedOriginalTransform;
272
+ el.style.opacity = pressedOriginalOpacity;
273
+ el.style.transition = pressedOriginalTransition;
274
+ }
241
275
 
242
- function onStart(e) {
276
+ function onTouchStart(e) {
243
277
  try {
244
278
  var target = findInteractive(e.target);
245
279
  if (!target) return;
246
280
  if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }
247
- target.classList.remove('rk-released');
248
- target.classList.add('rk-pressed');
281
+ if (pressed && pressed !== target) { resetStyle(pressed); }
282
+ applyPressedStyle(target);
249
283
  pressed = target;
250
- } catch(err) { console.log('[RK] touch error:', err); }
284
+ } catch(err) {}
251
285
  }
252
286
 
253
- function onEnd(e) {
287
+ function onTouchEnd(e) {
254
288
  try {
255
289
  if (!pressed) return;
256
290
  var t = pressed;
257
- t.classList.remove('rk-pressed');
258
- t.classList.add('rk-released');
291
+ applyReleasedStyle(t);
259
292
  releaseTimer = setTimeout(function() {
260
- t.classList.remove('rk-released');
293
+ resetStyle(t);
261
294
  releaseTimer = null;
262
295
  }, 300);
263
296
  pressed = null;
264
297
  } catch(err) {}
265
298
  }
266
299
 
267
- function onCancel(e) {
300
+ function onTouchCancel(e) {
268
301
  try {
269
302
  if (!pressed) return;
270
- pressed.classList.remove('rk-pressed', 'rk-released');
303
+ resetStyle(pressed);
271
304
  pressed = null;
272
305
  if (releaseTimer) { clearTimeout(releaseTimer); releaseTimer = null; }
273
306
  } catch(err) {}
274
307
  }
275
308
 
276
- // Capture phase for immediate response
277
- document.addEventListener('touchstart', onStart, { passive: true, capture: true });
278
- document.addEventListener('touchend', onEnd, { passive: true, capture: true });
279
- document.addEventListener('touchcancel', onCancel, { passive: true, capture: true });
280
- document.addEventListener('mousedown', onStart, { passive: true, capture: true });
281
- document.addEventListener('mouseup', onEnd, { passive: true, capture: true });
309
+ // Use capture phase for immediate response before any other handlers
310
+ document.addEventListener('touchstart', onTouchStart, { passive: true, capture: true });
311
+ document.addEventListener('touchend', onTouchEnd, { passive: true, capture: true });
312
+ document.addEventListener('touchcancel', onTouchCancel, { passive: true, capture: true });
313
+ // Mouse events for testing
314
+ document.addEventListener('mousedown', onTouchStart, { passive: true, capture: true });
315
+ document.addEventListener('mouseup', onTouchEnd, { passive: true, capture: true });
282
316
 
283
- console.log('[RK] Button animations initialized');
284
- } catch (err) { console.log('[RK] Button anim error:', err); }
317
+ } catch (err) {}
285
318
  true;
286
319
  })();
287
320
  `;
@@ -384,16 +417,177 @@ function preloadRampkitOverlay(opts) {
384
417
  opacity: 0,
385
418
  top: -1000,
386
419
  left: -1000,
387
- }, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
420
+ }, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening + exports.injectedButtonAnimations, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
388
421
  preloadSibling = new react_native_root_siblings_1.default((0, jsx_runtime_1.jsx)(HiddenPreloader, {}));
389
422
  }
390
423
  catch (e) {
391
424
  // best-effort preloading; ignore errors
392
425
  }
393
426
  }
427
+ /**
428
+ * Evaluate a comparison condition against variables
429
+ * Supports: ==, !=, >, <, >=, <=, and truthy checks
430
+ */
431
+ function evaluateCondition(condition, vars) {
432
+ condition = condition.trim();
433
+ // Match comparison operators: ==, !=, >=, <=, >, <
434
+ const comparisonMatch = condition.match(/^([A-Za-z_][A-Za-z0-9_.]*)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
435
+ if (comparisonMatch) {
436
+ const [, varName, operator, rawRight] = comparisonMatch;
437
+ const leftValue = vars.hasOwnProperty(varName) ? vars[varName] : undefined;
438
+ let rightValue = rawRight.trim();
439
+ // Parse right side - could be a quoted string or a number or a variable
440
+ if ((rightValue.startsWith('"') && rightValue.endsWith('"')) ||
441
+ (rightValue.startsWith("'") && rightValue.endsWith("'"))) {
442
+ // Quoted string literal
443
+ rightValue = rightValue.slice(1, -1);
444
+ }
445
+ else if (!isNaN(Number(rightValue))) {
446
+ // Numeric literal
447
+ rightValue = Number(rightValue);
448
+ }
449
+ else if (rightValue === "true") {
450
+ rightValue = true;
451
+ }
452
+ else if (rightValue === "false") {
453
+ rightValue = false;
454
+ }
455
+ else if (rightValue === "null") {
456
+ rightValue = null;
457
+ }
458
+ else if (vars.hasOwnProperty(rightValue)) {
459
+ // Variable reference
460
+ rightValue = vars[rightValue];
461
+ }
462
+ // Perform comparison
463
+ switch (operator) {
464
+ case "==":
465
+ return leftValue == rightValue;
466
+ case "!=":
467
+ return leftValue != rightValue;
468
+ case ">":
469
+ return Number(leftValue) > Number(rightValue);
470
+ case "<":
471
+ return Number(leftValue) < Number(rightValue);
472
+ case ">=":
473
+ return Number(leftValue) >= Number(rightValue);
474
+ case "<=":
475
+ return Number(leftValue) <= Number(rightValue);
476
+ default:
477
+ return false;
478
+ }
479
+ }
480
+ // Truthy check - just the variable name
481
+ const varName = condition.trim();
482
+ if (vars.hasOwnProperty(varName)) {
483
+ const value = vars[varName];
484
+ // Consider empty string as falsy
485
+ if (value === "")
486
+ return false;
487
+ return !!value;
488
+ }
489
+ // Unknown variable - treat as falsy
490
+ return false;
491
+ }
492
+ /**
493
+ * Parse a ternary value (the part after ? or after :)
494
+ * Returns the resolved value, handling both quoted strings and variable references
495
+ */
496
+ function parseTernaryValue(value, vars) {
497
+ value = value.trim();
498
+ // Check if it's a quoted string
499
+ if ((value.startsWith('"') && value.endsWith('"')) ||
500
+ (value.startsWith("'") && value.endsWith("'"))) {
501
+ return value.slice(1, -1);
502
+ }
503
+ // Otherwise treat as a variable reference
504
+ if (vars.hasOwnProperty(value)) {
505
+ const varValue = vars[value];
506
+ if (varValue === undefined || varValue === null)
507
+ return "";
508
+ if (typeof varValue === "boolean")
509
+ return varValue ? "true" : "false";
510
+ if (typeof varValue === "object")
511
+ return JSON.stringify(varValue);
512
+ return String(varValue);
513
+ }
514
+ // Return as-is if not found (could be a literal like a number)
515
+ return value;
516
+ }
517
+ /**
518
+ * Parse a ternary expression and find the colon that separates true/false values
519
+ * Handles nested quotes properly
520
+ */
521
+ function splitTernary(expr) {
522
+ // Find the ? that starts the ternary
523
+ let questionIdx = -1;
524
+ let inQuote = false;
525
+ let quoteChar = "";
526
+ for (let i = 0; i < expr.length; i++) {
527
+ const char = expr[i];
528
+ const prevChar = i > 0 ? expr[i - 1] : "";
529
+ if ((char === '"' || char === "'") && prevChar !== "\\") {
530
+ if (!inQuote) {
531
+ inQuote = true;
532
+ quoteChar = char;
533
+ }
534
+ else if (char === quoteChar) {
535
+ inQuote = false;
536
+ }
537
+ }
538
+ if (!inQuote && char === "?") {
539
+ questionIdx = i;
540
+ break;
541
+ }
542
+ }
543
+ if (questionIdx === -1)
544
+ return null;
545
+ const condition = expr.slice(0, questionIdx).trim();
546
+ const rest = expr.slice(questionIdx + 1);
547
+ // Find the : that separates true/false values
548
+ let colonIdx = -1;
549
+ inQuote = false;
550
+ quoteChar = "";
551
+ for (let i = 0; i < rest.length; i++) {
552
+ const char = rest[i];
553
+ const prevChar = i > 0 ? rest[i - 1] : "";
554
+ if ((char === '"' || char === "'") && prevChar !== "\\") {
555
+ if (!inQuote) {
556
+ inQuote = true;
557
+ quoteChar = char;
558
+ }
559
+ else if (char === quoteChar) {
560
+ inQuote = false;
561
+ }
562
+ }
563
+ if (!inQuote && char === ":") {
564
+ colonIdx = i;
565
+ break;
566
+ }
567
+ }
568
+ if (colonIdx === -1)
569
+ return null;
570
+ const trueValue = rest.slice(0, colonIdx).trim();
571
+ const falseValue = rest.slice(colonIdx + 1).trim();
572
+ return { condition, trueValue, falseValue };
573
+ }
394
574
  /**
395
575
  * Resolve device/user templates in a string
396
- * Replaces ${device.xxx} and ${user.xxx} with actual values from context
576
+ * Supports both simple variables ${varName} and conditional ternary expressions
577
+ * ${condition ? "trueValue" : "falseValue"}
578
+ *
579
+ * Supported operators in conditions:
580
+ * - == (equals)
581
+ * - != (not equals)
582
+ * - > (greater than)
583
+ * - < (less than)
584
+ * - >= (greater or equal)
585
+ * - <= (less or equal)
586
+ * - Truthy check (just variable name)
587
+ *
588
+ * Values can be:
589
+ * - Quoted strings: "hello" or 'hello'
590
+ * - Variable references: username
397
591
  */
398
592
  function resolveContextTemplates(text, context) {
399
593
  if (!text || !text.includes("${"))
@@ -413,8 +607,23 @@ function resolveContextTemplates(text, context) {
413
607
  });
414
608
  }
415
609
  console.log("[RampKit] Resolving templates with vars:", JSON.stringify(vars));
416
- // Replace ${varName} patterns
417
- return text.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, varName) => {
610
+ // Match ${...} expressions - use a more permissive regex to capture full expressions
611
+ // including ternary operators with quotes
612
+ return text.replace(/\$\{([^}]+)\}/g, (match, innerExpr) => {
613
+ const expr = innerExpr.trim();
614
+ // Check if this is a ternary expression
615
+ const ternary = splitTernary(expr);
616
+ if (ternary) {
617
+ const { condition, trueValue, falseValue } = ternary;
618
+ const result = evaluateCondition(condition, vars);
619
+ const value = result
620
+ ? parseTernaryValue(trueValue, vars)
621
+ : parseTernaryValue(falseValue, vars);
622
+ console.log(`[RampKit] Ternary: ${condition} ? ${trueValue} : ${falseValue} => ${result} => "${value}"`);
623
+ return value;
624
+ }
625
+ // Simple variable substitution
626
+ const varName = expr;
418
627
  if (vars.hasOwnProperty(varName)) {
419
628
  const value = vars[varName];
420
629
  console.log(`[RampKit] Replacing ${match} with:`, value);
@@ -939,7 +1148,7 @@ function Overlay(props) {
939
1148
  opacity: pagerOpacity,
940
1149
  transform: [{ translateX: pagerTranslateX }],
941
1150
  },
942
- ], children: (0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.page, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: styles.webview, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
1151
+ ], children: (0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.page, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: styles.webview, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening + exports.injectedButtonAnimations, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
943
1152
  setLoadedCount((c) => c + 1);
944
1153
  if (i === 0) {
945
1154
  setFirstPageLoaded(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "The Expo SDK for RampKit. Build, test, and personalize app onboardings with instant updates.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",