@tagadapay/plugin-sdk 3.0.9 → 3.0.12

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.
@@ -588,113 +588,220 @@ export class TagadaClient {
588
588
  applyStylesToElement(elementId, styles) {
589
589
  if (typeof document === 'undefined')
590
590
  return;
591
- // Support multiple IDs in editor-id attribute (space-separated)
592
- // Use ~= selector to match elementId as a space-separated value
593
- const element = document.querySelector(`[editor-id~="${elementId}"]`);
594
- if (!element) {
595
- if (this.state.debugMode) {
596
- console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found`);
597
- }
598
- return;
591
+ // Always remove any existing highlight, even if the target element is not found
592
+ const staticHighlightId = 'tagada-editor-highlight';
593
+ const existingHighlight = document.getElementById(staticHighlightId);
594
+ if (existingHighlight) {
595
+ existingHighlight.remove();
599
596
  }
600
- // List of void/self-closing elements that don't accept children
601
- const voidElements = new Set([
602
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
603
- 'link', 'meta', 'param', 'source', 'track', 'wbr'
604
- ]);
605
- const isVoidElement = voidElements.has(element.tagName.toLowerCase());
606
- // For void elements, wrap them in a container div
607
- let targetElement = element;
608
- let wrapper = null;
609
- if (isVoidElement) {
610
- // Check if element is already wrapped (from a previous highlight)
611
- const parent = element.parentElement;
612
- const existingWrapper = parent?.getAttribute('data-tagada-highlight-wrapper');
613
- if (existingWrapper === 'true') {
614
- // Reuse existing wrapper
615
- targetElement = parent;
597
+ const applyToElement = (element) => {
598
+ // List of void/self-closing elements that don't accept children
599
+ const voidElements = new Set([
600
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
601
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'
602
+ ]);
603
+ const isVoidElement = voidElements.has(element.tagName.toLowerCase());
604
+ // For void elements, wrap them in a container div
605
+ let targetElement = element;
606
+ if (isVoidElement) {
607
+ // Check if element is already wrapped (from a previous highlight)
608
+ const parent = element.parentElement;
609
+ const existingWrapper = parent?.getAttribute('data-tagada-highlight-wrapper');
610
+ if (existingWrapper === 'true' && parent instanceof HTMLElement) {
611
+ // Reuse existing wrapper
612
+ targetElement = parent;
613
+ }
614
+ else {
615
+ // Create a new wrapper
616
+ const newWrapper = document.createElement('div');
617
+ newWrapper.setAttribute('data-tagada-highlight-wrapper', 'true');
618
+ // Preserve the element's layout behavior by matching its display style
619
+ // This minimizes the visual impact of the wrapper
620
+ const computedStyle = window.getComputedStyle(element);
621
+ const elementDisplay = computedStyle.display;
622
+ // Match the display type to preserve layout
623
+ // For inline elements, use inline-block (to support position: relative)
624
+ // For all others, use the same display value
625
+ if (elementDisplay === 'inline') {
626
+ newWrapper.style.display = 'inline-block';
627
+ }
628
+ else {
629
+ newWrapper.style.display = elementDisplay;
630
+ }
631
+ newWrapper.style.position = 'relative';
632
+ // Preserve vertical alignment for inline elements
633
+ if (elementDisplay === 'inline' || elementDisplay.includes('inline')) {
634
+ const verticalAlign = computedStyle.verticalAlign;
635
+ if (verticalAlign && verticalAlign !== 'baseline') {
636
+ newWrapper.style.verticalAlign = verticalAlign;
637
+ }
638
+ }
639
+ // Preserve spacing and layout properties from the original element
640
+ // List of CSS properties that affect spacing and layout
641
+ const spacingProperties = [
642
+ // Width and height
643
+ 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
644
+ // Flex properties
645
+ 'flex', 'flexGrow', 'flexShrink', 'flexBasis',
646
+ // Grid properties
647
+ 'gridColumn', 'gridRow', 'gridColumnStart', 'gridColumnEnd',
648
+ 'gridRowStart', 'gridRowEnd', 'gridArea',
649
+ // Alignment properties
650
+ 'alignSelf', 'justifySelf',
651
+ // Box sizing
652
+ 'boxSizing',
653
+ // Gap (for grid/flex)
654
+ 'gap', 'rowGap', 'columnGap',
655
+ // Order (for flex/grid)
656
+ 'order',
657
+ // Aspect ratio
658
+ 'aspectRatio',
659
+ ];
660
+ spacingProperties.forEach(prop => {
661
+ const camelProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
662
+ const value = computedStyle.getPropertyValue(camelProp);
663
+ // Only copy if the value is not empty
664
+ // For most properties, we want to preserve the value even if it's 'auto' or '0px'
665
+ // as these might be intentional layout choices
666
+ if (value && value.trim() !== '') {
667
+ // Handle special cases
668
+ if (prop === 'flex' && value !== 'none' && value !== '0 1 auto') {
669
+ newWrapper.style.flex = value;
670
+ }
671
+ else if (prop === 'boxSizing') {
672
+ newWrapper.style.boxSizing = value;
673
+ }
674
+ else {
675
+ // Use setProperty for kebab-case properties
676
+ // Preserve all values including percentages, auto, etc.
677
+ newWrapper.style.setProperty(camelProp, value);
678
+ }
679
+ }
680
+ });
681
+ // Also check inline styles for spacing properties
682
+ // This handles cases where styles are set directly on the element
683
+ if (element.style) {
684
+ const inlineSpacingProps = [
685
+ 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
686
+ 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
687
+ 'grid-column', 'grid-row', 'grid-column-start', 'grid-column-end',
688
+ 'grid-row-start', 'grid-row-end', 'grid-area',
689
+ 'align-self', 'justify-self',
690
+ 'box-sizing', 'gap', 'row-gap', 'column-gap', 'order', 'aspect-ratio'
691
+ ];
692
+ inlineSpacingProps.forEach(prop => {
693
+ const inlineValue = element.style.getPropertyValue(prop);
694
+ if (inlineValue) {
695
+ newWrapper.style.setProperty(prop, inlineValue);
696
+ }
697
+ });
698
+ }
699
+ // Insert wrapper before element
700
+ element.parentNode?.insertBefore(newWrapper, element);
701
+ // Move element into wrapper
702
+ newWrapper.appendChild(element);
703
+ targetElement = newWrapper;
704
+ }
616
705
  }
617
- else {
618
- // Create a new wrapper
619
- wrapper = document.createElement('div');
620
- wrapper.setAttribute('data-tagada-highlight-wrapper', 'true');
621
- // Preserve the element's layout behavior by matching its display style
622
- // This minimizes the visual impact of the wrapper
623
- const computedStyle = window.getComputedStyle(element);
624
- const elementDisplay = computedStyle.display;
625
- // Match the display type to preserve layout
626
- // For inline elements, use inline-block (to support position: relative)
627
- // For all others, use the same display value
628
- if (elementDisplay === 'inline') {
629
- wrapper.style.display = 'inline-block';
706
+ // Ensure element has position relative for absolute child positioning
707
+ const computedStyle = getComputedStyle(isVoidElement ? element : targetElement);
708
+ if (targetElement.style.position === 'static' || !targetElement.style.position) {
709
+ targetElement.style.position = 'relative';
710
+ }
711
+ // Create new highlight div with static ID
712
+ const highlightDiv = document.createElement('div');
713
+ highlightDiv.id = staticHighlightId;
714
+ highlightDiv.style.position = 'absolute';
715
+ highlightDiv.style.inset = '0';
716
+ highlightDiv.style.pointerEvents = 'none';
717
+ highlightDiv.style.zIndex = '9999';
718
+ highlightDiv.style.boxSizing = 'border-box';
719
+ highlightDiv.style.background = 'none';
720
+ highlightDiv.style.border = 'none';
721
+ highlightDiv.style.outline = 'none';
722
+ highlightDiv.style.margin = '0';
723
+ highlightDiv.style.padding = '0';
724
+ // Inherit border radius from parent
725
+ const borderRadius = computedStyle.borderRadius;
726
+ if (borderRadius) {
727
+ highlightDiv.style.borderRadius = borderRadius;
728
+ }
729
+ // Append the new highlight div to the target element
730
+ targetElement.appendChild(highlightDiv);
731
+ // Apply CSS properties to the highlight div
732
+ const stylesToApply = styles || { boxShadow: '0 0 0 2px rgb(239 68 68)' }; // Default red ring
733
+ Object.entries(stylesToApply).forEach(([property, value]) => {
734
+ // Convert kebab-case to camelCase for style object
735
+ const camelProperty = property.includes('-')
736
+ ? property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
737
+ : property;
738
+ // Use setProperty for kebab-case or direct assignment for camelCase
739
+ if (property.includes('-')) {
740
+ highlightDiv.style.setProperty(property, value);
630
741
  }
631
742
  else {
632
- wrapper.style.display = elementDisplay;
743
+ highlightDiv.style[camelProperty] = value;
633
744
  }
634
- wrapper.style.position = 'relative';
635
- // Preserve vertical alignment for inline elements
636
- if (elementDisplay === 'inline' || elementDisplay.includes('inline')) {
637
- const verticalAlign = computedStyle.verticalAlign;
638
- if (verticalAlign && verticalAlign !== 'baseline') {
639
- wrapper.style.verticalAlign = verticalAlign;
745
+ });
746
+ // Scroll element into view
747
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
748
+ if (this.state.debugMode) {
749
+ const appliedStyles = Object.entries(stylesToApply).map(([k, v]) => `${k}: ${v}`).join('; ');
750
+ console.log(`[TagadaClient] Applied styles to highlight div of element #${elementId}:`, appliedStyles);
751
+ }
752
+ };
753
+ // Support multiple IDs in editor-id attribute (space-separated)
754
+ // Use ~= selector to match elementId as a space-separated value
755
+ const maxAttempts = 5;
756
+ const intervalMs = 1000;
757
+ let attempts = 0;
758
+ const isElementHidden = (element) => {
759
+ const style = window.getComputedStyle(element);
760
+ return (style.display === 'none' ||
761
+ style.visibility === 'hidden' ||
762
+ style.opacity === '0' ||
763
+ element.hidden ||
764
+ element.offsetWidth === 0 ||
765
+ element.offsetHeight === 0);
766
+ };
767
+ const findAndApply = () => {
768
+ const elements = document.querySelectorAll(`[editor-id~="${elementId}"]`);
769
+ if (elements.length === 0) {
770
+ attempts += 1;
771
+ if (attempts >= maxAttempts) {
772
+ if (this.state.debugMode) {
773
+ console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found after ${maxAttempts} attempts`);
640
774
  }
775
+ return;
641
776
  }
642
- // Insert wrapper before element
643
- element.parentNode?.insertBefore(wrapper, element);
644
- // Move element into wrapper
645
- wrapper.appendChild(element);
646
- targetElement = wrapper;
777
+ if (this.state.debugMode) {
778
+ console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found (attempt ${attempts}/${maxAttempts}), retrying in ${intervalMs / 1000}s`);
779
+ }
780
+ setTimeout(findAndApply, intervalMs);
781
+ return;
647
782
  }
648
- }
649
- // Ensure element has position relative for absolute child positioning
650
- const computedStyle = getComputedStyle(isVoidElement ? element : targetElement);
651
- if (targetElement.style.position === 'static' || !targetElement.style.position) {
652
- targetElement.style.position = 'relative';
653
- }
654
- // Remove any existing highlight div before creating a new one
655
- const staticHighlightId = 'tagada-editor-highlight';
656
- const existingHighlight = document.getElementById(staticHighlightId);
657
- if (existingHighlight) {
658
- existingHighlight.remove();
659
- }
660
- // Create new highlight div with static ID
661
- const highlightDiv = document.createElement('div');
662
- highlightDiv.id = staticHighlightId;
663
- highlightDiv.style.position = 'absolute';
664
- highlightDiv.style.inset = '0';
665
- highlightDiv.style.pointerEvents = 'none';
666
- highlightDiv.style.zIndex = '9999';
667
- highlightDiv.style.boxSizing = 'border-box';
668
- highlightDiv.style.background = 'none';
669
- highlightDiv.style.border = 'none';
670
- highlightDiv.style.outline = 'none';
671
- // Inherit border radius from parent
672
- const borderRadius = computedStyle.borderRadius;
673
- if (borderRadius) {
674
- highlightDiv.style.borderRadius = borderRadius;
675
- }
676
- // Append the new highlight div to the target element
677
- targetElement.appendChild(highlightDiv);
678
- // Apply CSS properties to the highlight div
679
- const stylesToApply = styles || { boxShadow: '0 0 0 2px rgb(239 68 68)' }; // Default red ring
680
- Object.entries(stylesToApply).forEach(([property, value]) => {
681
- // Convert kebab-case to camelCase for style object
682
- const camelProperty = property.includes('-')
683
- ? property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
684
- : property;
685
- // Use setProperty for kebab-case or direct assignment for camelCase
686
- if (property.includes('-')) {
687
- highlightDiv.style.setProperty(property, value);
783
+ // If multiple elements found, prioritize the one that is not hidden
784
+ let element = null;
785
+ if (elements.length === 1) {
786
+ element = elements[0];
688
787
  }
689
788
  else {
690
- highlightDiv.style[camelProperty] = value;
789
+ // Find the first non-hidden element
790
+ for (let i = 0; i < elements.length; i++) {
791
+ if (!isElementHidden(elements[i])) {
792
+ element = elements[i];
793
+ break;
794
+ }
795
+ }
796
+ // If all are hidden, use the first one
797
+ if (!element) {
798
+ element = elements[0];
799
+ }
691
800
  }
692
- });
693
- // Scroll element into view
694
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
695
- if (this.state.debugMode) {
696
- const appliedStyles = Object.entries(stylesToApply).map(([k, v]) => `${k}: ${v}`).join('; ');
697
- console.log(`[TagadaClient] Applied styles to highlight div of element #${elementId}:`, appliedStyles);
698
- }
801
+ if (element) {
802
+ applyToElement(element);
803
+ }
804
+ };
805
+ findAndApply();
699
806
  }
700
807
  }
@@ -79,6 +79,7 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
79
79
  const [activeTab, setActiveTab] = useState('overview');
80
80
  const [runningScripts, setRunningScripts] = useState(new Set());
81
81
  const [scriptResults, setScriptResults] = useState({});
82
+ const [expandedScriptCategories, setExpandedScriptCategories] = useState({});
82
83
  // Handler to jump to a specific step using direct_navigation
83
84
  const handleJumpToStep = async (stepId, stepName) => {
84
85
  // Try to get sessionId from debug data or context
@@ -187,8 +188,9 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
187
188
  const script = context.debugScripts?.find((s) => s.id === scriptId);
188
189
  if (!script)
189
190
  return;
191
+ // Clear previous script results so only the current script shows a status
192
+ setScriptResults({});
190
193
  setRunningScripts((prev) => new Set(prev).add(scriptId));
191
- setScriptResults((prev) => ({ ...prev, [scriptId]: undefined }));
192
194
  try {
193
195
  await script.run(context);
194
196
  setScriptResults((prev) => ({
@@ -733,51 +735,71 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
733
735
  acc[category].push(script);
734
736
  return acc;
735
737
  }, {});
736
- return Object.entries(grouped).map(([category, scripts]) => (_jsxs("div", { children: [_jsx("div", { style: {
737
- fontSize: '11px',
738
- color: '#9ca3af',
739
- fontWeight: 'bold',
740
- marginBottom: '8px',
741
- textTransform: 'uppercase',
742
- letterSpacing: '0.5px',
743
- }, children: category }), _jsx("div", { style: { display: 'grid', gap: '8px' }, children: scripts.map((script) => {
744
- const isRunning = runningScripts.has(script.id);
745
- const result = scriptResults[script.id];
746
- return (_jsxs("div", { style: {
747
- border: '1px solid #374151',
748
- borderRadius: '6px',
749
- padding: '10px 12px',
750
- backgroundColor: '#111827',
751
- }, children: [_jsxs("div", { style: {
752
- display: 'flex',
753
- justifyContent: 'space-between',
754
- alignItems: 'flex-start',
755
- gap: '12px',
756
- }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx("div", { style: {
757
- color: '#f9fafb',
758
- fontWeight: 'bold',
759
- fontSize: '12px',
760
- marginBottom: '4px',
761
- }, children: script.name }), script.description && (_jsx("div", { style: { fontSize: '11px', color: '#9ca3af' }, children: script.description }))] }), _jsx("button", { onClick: () => handleRunScript(script.id), disabled: isRunning, style: {
762
- backgroundColor: isRunning ? '#374151' : '#3b82f6',
763
- color: '#fff',
764
- border: 'none',
765
- borderRadius: '4px',
766
- padding: '6px 12px',
767
- fontSize: '11px',
768
- fontWeight: 'bold',
769
- cursor: isRunning ? 'not-allowed' : 'pointer',
770
- transition: 'background-color 0.2s',
771
- minWidth: '60px',
772
- }, children: isRunning ? '' : '▶ Run' })] }), result && (_jsxs("div", { style: {
773
- marginTop: '8px',
774
- padding: '6px 8px',
775
- borderRadius: '4px',
776
- fontSize: '10px',
777
- backgroundColor: result.success ? '#065f46' : '#7f1d1d',
778
- color: result.success ? '#6ee7b7' : '#fca5a5',
779
- }, children: [result.success ? '' : '❌', " ", result.message] }))] }, script.id));
780
- }) })] }, category)));
738
+ return Object.entries(grouped).map(([category, scripts]) => {
739
+ const isExpanded = expandedScriptCategories[category] ?? false;
740
+ const toggleCategory = () => {
741
+ setExpandedScriptCategories((prev) => ({
742
+ ...prev,
743
+ [category]: !(prev[category] ?? false),
744
+ }));
745
+ };
746
+ return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggleCategory, style: {
747
+ display: 'flex',
748
+ alignItems: 'center',
749
+ justifyContent: 'space-between',
750
+ width: '100%',
751
+ background: 'none',
752
+ border: 'none',
753
+ padding: '6px 0',
754
+ cursor: 'pointer',
755
+ }, children: _jsxs("div", { style: {
756
+ display: 'flex',
757
+ alignItems: 'center',
758
+ gap: '6px',
759
+ fontSize: '11px',
760
+ fontWeight: 'bold',
761
+ textTransform: 'uppercase',
762
+ letterSpacing: '0.5px',
763
+ color: '#9ca3af',
764
+ }, children: [_jsx("span", { style: { fontSize: '10px' }, children: isExpanded ? '' : '▶' }), _jsx("span", { children: category }), _jsxs("span", { style: { fontSize: '10px', color: '#6b7280' }, children: ["(", scripts.length, ")"] })] }) }), isExpanded && (_jsx("div", { style: { display: 'grid', gap: '8px', marginTop: '4px' }, children: scripts.map((script) => {
765
+ const isRunning = runningScripts.has(script.id);
766
+ const result = scriptResults[script.id];
767
+ return (_jsxs("div", { style: {
768
+ border: '1px solid #374151',
769
+ borderRadius: '6px',
770
+ padding: '10px 12px',
771
+ backgroundColor: '#111827',
772
+ }, children: [_jsxs("div", { style: {
773
+ display: 'flex',
774
+ justifyContent: 'space-between',
775
+ alignItems: 'flex-start',
776
+ gap: '12px',
777
+ }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx("div", { style: {
778
+ color: '#f9fafb',
779
+ fontWeight: 'bold',
780
+ fontSize: '12px',
781
+ marginBottom: '4px',
782
+ }, children: script.name }), script.description && (_jsx("div", { style: { fontSize: '11px', color: '#9ca3af' }, children: script.description }))] }), _jsx("button", { onClick: () => handleRunScript(script.id), disabled: isRunning, style: {
783
+ backgroundColor: isRunning ? '#374151' : '#3b82f6',
784
+ color: '#fff',
785
+ border: 'none',
786
+ borderRadius: '4px',
787
+ padding: '6px 12px',
788
+ fontSize: '11px',
789
+ fontWeight: 'bold',
790
+ cursor: isRunning ? 'not-allowed' : 'pointer',
791
+ transition: 'background-color 0.2s',
792
+ minWidth: '60px',
793
+ }, children: isRunning ? '⏳' : '▶ Run' })] }), result && (_jsxs("div", { style: {
794
+ marginTop: '8px',
795
+ padding: '6px 8px',
796
+ borderRadius: '4px',
797
+ fontSize: '10px',
798
+ backgroundColor: result.success ? '#065f46' : '#7f1d1d',
799
+ color: result.success ? '#6ee7b7' : '#fca5a5',
800
+ }, children: [result.success ? '✅' : '❌', " ", result.message] }))] }, script.id));
801
+ }) }))] }, category));
802
+ });
781
803
  })() })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No debug scripts available" }))] })), activeTab === 'raw' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Raw Context Data" }), _jsx("div", { style: { fontSize: '12px' }, children: _jsx(TreeView, { data: context, name: "tagadaContext", maxLevel: 4 }) })] }))] })] })] }));
782
804
  };
783
805
  export default DebugDrawer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "3.0.9",
3
+ "version": "3.0.12",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",