mdas-jsview-sdk 1.0.23-uat.0 → 1.0.26-uat.0
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.
- package/dist/mdas-sdk.esm.js +1004 -230
- package/dist/mdas-sdk.esm.js.map +1 -1
- package/dist/mdas-sdk.js +1004 -230
- package/dist/mdas-sdk.js.map +1 -1
- package/dist/mdas-sdk.min.js +9 -9
- package/dist/mdas-sdk.min.js.map +1 -1
- package/package.json +1 -1
package/dist/mdas-sdk.js
CHANGED
|
@@ -2863,8 +2863,36 @@
|
|
|
2863
2863
|
}
|
|
2864
2864
|
`;
|
|
2865
2865
|
|
|
2866
|
+
/**
|
|
2867
|
+
* @deprecated SharedStyles is deprecated and will be removed in a future version.
|
|
2868
|
+
*
|
|
2869
|
+
* Please migrate to BaseStyles + CommonWidgetPatterns:
|
|
2870
|
+
*
|
|
2871
|
+
* BEFORE:
|
|
2872
|
+
* import { SharedStyles } from './styles/index.js';
|
|
2873
|
+
* export const MyWidgetStyles = `${SharedStyles} ...widget styles...`;
|
|
2874
|
+
*
|
|
2875
|
+
* AFTER:
|
|
2876
|
+
* import { BaseStyles } from './styles/BaseStyles.js';
|
|
2877
|
+
* import { getLoadingOverlayStyles, getErrorStyles } from './styles/CommonWidgetPatterns.js';
|
|
2878
|
+
* export const MyWidgetStyles = `
|
|
2879
|
+
* ${BaseStyles}
|
|
2880
|
+
* ${getLoadingOverlayStyles('my-widget')}
|
|
2881
|
+
* ${getErrorStyles('my-widget')}
|
|
2882
|
+
* ...widget-specific styles...
|
|
2883
|
+
* `;
|
|
2884
|
+
*
|
|
2885
|
+
* Benefits of migration:
|
|
2886
|
+
* - Proper CSS scoping (no global conflicts)
|
|
2887
|
+
* - Reduced code duplication
|
|
2888
|
+
* - Better maintainability
|
|
2889
|
+
* - Smaller bundle size
|
|
2890
|
+
*
|
|
2891
|
+
* See STYLING_CUSTOMIZATION_GUIDE.md for details.
|
|
2892
|
+
*/
|
|
2866
2893
|
const SharedStyles = `
|
|
2867
2894
|
/* ========================================
|
|
2895
|
+
⚠️ DEPRECATED - Use BaseStyles.js instead
|
|
2868
2896
|
FONT SIZE SYSTEM - CSS Variables
|
|
2869
2897
|
Adjust --mdas-base-font-size to scale all fonts
|
|
2870
2898
|
======================================== */
|
|
@@ -6644,6 +6672,15 @@
|
|
|
6644
6672
|
</select>
|
|
6645
6673
|
<button class="fetch-button" disabled>Search</button>
|
|
6646
6674
|
</div>
|
|
6675
|
+
<div class="filter-section">
|
|
6676
|
+
<label for="strike-filter" class="filter-label">Display:</label>
|
|
6677
|
+
<select class="strike-filter" id="strike-filter">
|
|
6678
|
+
<option value="5">Near the money (±5 strikes)</option>
|
|
6679
|
+
<option value="10">Near the money (±10 strikes)</option>
|
|
6680
|
+
<option value="15">Near the money (±15 strikes)</option>
|
|
6681
|
+
<option value="all">All strikes</option>
|
|
6682
|
+
</select>
|
|
6683
|
+
</div>
|
|
6647
6684
|
</div>
|
|
6648
6685
|
|
|
6649
6686
|
<!-- Data Grid Section -->
|
|
@@ -6700,11 +6737,383 @@
|
|
|
6700
6737
|
</div>
|
|
6701
6738
|
`;
|
|
6702
6739
|
|
|
6740
|
+
// src/widgets/styles/BaseStyles.js
|
|
6741
|
+
|
|
6742
|
+
/**
|
|
6743
|
+
* Base Styles - CSS Variables and Universal Utilities
|
|
6744
|
+
*
|
|
6745
|
+
* This file contains:
|
|
6746
|
+
* 1. CSS Custom Properties (variables) for consistent theming
|
|
6747
|
+
* 2. Responsive font sizing system
|
|
6748
|
+
* 3. Optional utility classes
|
|
6749
|
+
*
|
|
6750
|
+
* Usage:
|
|
6751
|
+
* import { BaseStyles } from './styles/BaseStyles';
|
|
6752
|
+
* export const MyWidgetStyles = `${BaseStyles} ...widget styles...`;
|
|
6753
|
+
*/
|
|
6754
|
+
|
|
6755
|
+
const BaseStyles = `
|
|
6756
|
+
/* ============================================
|
|
6757
|
+
MDAS WIDGET CSS VARIABLES
|
|
6758
|
+
============================================ */
|
|
6759
|
+
|
|
6760
|
+
:root {
|
|
6761
|
+
/* --- FONT SIZE SYSTEM --- */
|
|
6762
|
+
/* Base font size - all other sizes are relative to this */
|
|
6763
|
+
--mdas-base-font-size: 14px;
|
|
6764
|
+
|
|
6765
|
+
/* Component-specific font sizes (em units scale with base) */
|
|
6766
|
+
--mdas-small-text-size: 0.79em; /* 11px at 14px base */
|
|
6767
|
+
--mdas-medium-text-size: 0.93em; /* 13px at 14px base */
|
|
6768
|
+
--mdas-large-text-size: 1.14em; /* 16px at 14px base */
|
|
6769
|
+
|
|
6770
|
+
/* Widget element sizes */
|
|
6771
|
+
--mdas-company-name-size: 1.43em; /* 20px at 14px base */
|
|
6772
|
+
--mdas-symbol-size: 1.79em; /* 25px at 14px base */
|
|
6773
|
+
--mdas-price-size: 2.29em; /* 32px at 14px base */
|
|
6774
|
+
--mdas-change-size: 1.14em; /* 16px at 14px base */
|
|
6775
|
+
|
|
6776
|
+
/* Data display sizes */
|
|
6777
|
+
--mdas-label-size: 0.86em; /* 12px at 14px base */
|
|
6778
|
+
--mdas-value-size: 1em; /* 14px at 14px base */
|
|
6779
|
+
--mdas-footer-size: 0.79em; /* 11px at 14px base */
|
|
6780
|
+
|
|
6781
|
+
/* Chart-specific sizes */
|
|
6782
|
+
--mdas-chart-title-size: 1.29em; /* 18px at 14px base */
|
|
6783
|
+
--mdas-chart-label-size: 0.93em; /* 13px at 14px base */
|
|
6784
|
+
--mdas-chart-value-size: 1.43em; /* 20px at 14px base */
|
|
6785
|
+
|
|
6786
|
+
/* --- COLOR PALETTE (for future theming support) --- */
|
|
6787
|
+
/* Primary colors */
|
|
6788
|
+
--mdas-primary-color: #3b82f6;
|
|
6789
|
+
--mdas-primary-hover: #2563eb;
|
|
6790
|
+
|
|
6791
|
+
/* Status colors */
|
|
6792
|
+
--mdas-color-positive: #059669;
|
|
6793
|
+
--mdas-color-positive-bg: #d1fae5;
|
|
6794
|
+
--mdas-color-negative: #dc2626;
|
|
6795
|
+
--mdas-color-negative-bg: #fee2e2;
|
|
6796
|
+
--mdas-color-neutral: #6b7280;
|
|
6797
|
+
|
|
6798
|
+
/* Background colors */
|
|
6799
|
+
--mdas-bg-primary: #ffffff;
|
|
6800
|
+
--mdas-bg-secondary: #f9fafb;
|
|
6801
|
+
--mdas-bg-tertiary: #f3f4f6;
|
|
6802
|
+
|
|
6803
|
+
/* Text colors */
|
|
6804
|
+
--mdas-text-primary: #111827;
|
|
6805
|
+
--mdas-text-secondary: #6b7280;
|
|
6806
|
+
--mdas-text-tertiary: #9ca3af;
|
|
6807
|
+
|
|
6808
|
+
/* Border colors */
|
|
6809
|
+
--mdas-border-primary: #e5e7eb;
|
|
6810
|
+
--mdas-border-secondary: #d1d5db;
|
|
6811
|
+
|
|
6812
|
+
/* --- SPACING SYSTEM --- */
|
|
6813
|
+
--mdas-spacing-xs: 4px;
|
|
6814
|
+
--mdas-spacing-sm: 8px;
|
|
6815
|
+
--mdas-spacing-md: 16px;
|
|
6816
|
+
--mdas-spacing-lg: 24px;
|
|
6817
|
+
--mdas-spacing-xl: 32px;
|
|
6818
|
+
|
|
6819
|
+
/* --- EFFECTS --- */
|
|
6820
|
+
--mdas-border-radius: 8px;
|
|
6821
|
+
--mdas-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
6822
|
+
--mdas-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
6823
|
+
--mdas-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
|
6824
|
+
|
|
6825
|
+
/* --- Z-INDEX LAYERS --- */
|
|
6826
|
+
--mdas-z-base: 1;
|
|
6827
|
+
--mdas-z-sticky: 10;
|
|
6828
|
+
--mdas-z-overlay: 100;
|
|
6829
|
+
--mdas-z-modal: 10000;
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6832
|
+
/* ============================================
|
|
6833
|
+
RESPONSIVE FONT SCALING
|
|
6834
|
+
============================================ */
|
|
6835
|
+
|
|
6836
|
+
/* Tablet breakpoint - slightly smaller fonts */
|
|
6837
|
+
@media (max-width: 768px) {
|
|
6838
|
+
:root {
|
|
6839
|
+
--mdas-base-font-size: 13px;
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6843
|
+
/* Mobile breakpoint - smaller fonts for small screens */
|
|
6844
|
+
@media (max-width: 480px) {
|
|
6845
|
+
:root {
|
|
6846
|
+
--mdas-base-font-size: 12px;
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
|
|
6850
|
+
/* ============================================
|
|
6851
|
+
OPTIONAL UTILITY CLASSES
|
|
6852
|
+
Use these classes for consistent styling
|
|
6853
|
+
============================================ */
|
|
6854
|
+
|
|
6855
|
+
/* Widget card styling - apply to widget root element */
|
|
6856
|
+
.mdas-card {
|
|
6857
|
+
background: var(--mdas-bg-primary);
|
|
6858
|
+
border-radius: var(--mdas-border-radius);
|
|
6859
|
+
padding: var(--mdas-spacing-lg);
|
|
6860
|
+
box-shadow: var(--mdas-shadow-md);
|
|
6861
|
+
border: 1px solid var(--mdas-border-primary);
|
|
6862
|
+
}
|
|
6863
|
+
|
|
6864
|
+
/* Responsive container */
|
|
6865
|
+
.mdas-container {
|
|
6866
|
+
width: 100%;
|
|
6867
|
+
max-width: 1400px;
|
|
6868
|
+
margin: 0 auto;
|
|
6869
|
+
}
|
|
6870
|
+
|
|
6871
|
+
/* Text utilities */
|
|
6872
|
+
.mdas-text-primary {
|
|
6873
|
+
color: var(--mdas-text-primary);
|
|
6874
|
+
}
|
|
6875
|
+
|
|
6876
|
+
.mdas-text-secondary {
|
|
6877
|
+
color: var(--mdas-text-secondary);
|
|
6878
|
+
}
|
|
6879
|
+
|
|
6880
|
+
.mdas-text-muted {
|
|
6881
|
+
color: var(--mdas-text-tertiary);
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
/* Status colors */
|
|
6885
|
+
.mdas-positive {
|
|
6886
|
+
color: var(--mdas-color-positive);
|
|
6887
|
+
}
|
|
6888
|
+
|
|
6889
|
+
.mdas-negative {
|
|
6890
|
+
color: var(--mdas-color-negative);
|
|
6891
|
+
}
|
|
6892
|
+
|
|
6893
|
+
.mdas-neutral {
|
|
6894
|
+
color: var(--mdas-color-neutral);
|
|
6895
|
+
}
|
|
6896
|
+
`;
|
|
6897
|
+
|
|
6898
|
+
// src/widgets/styles/CommonWidgetPatterns.js
|
|
6899
|
+
|
|
6900
|
+
/**
|
|
6901
|
+
* Common Widget Patterns - Reusable Style Functions
|
|
6902
|
+
*
|
|
6903
|
+
* These functions generate properly scoped CSS for common widget patterns.
|
|
6904
|
+
* Each function takes a widget class name and returns scoped CSS.
|
|
6905
|
+
*
|
|
6906
|
+
* Benefits:
|
|
6907
|
+
* - Eliminates code duplication
|
|
6908
|
+
* - Ensures proper scoping (no global conflicts)
|
|
6909
|
+
* - Consistent styling across widgets
|
|
6910
|
+
* - Easy to maintain and update
|
|
6911
|
+
*
|
|
6912
|
+
* Usage:
|
|
6913
|
+
* import { getLoadingOverlayStyles } from './CommonWidgetPatterns';
|
|
6914
|
+
* const styles = `${getLoadingOverlayStyles('my-widget')} ...other styles...`;
|
|
6915
|
+
*/
|
|
6916
|
+
|
|
6917
|
+
/**
|
|
6918
|
+
* Generate loading overlay styles for a widget
|
|
6919
|
+
* @param {string} widgetClass - Widget class name (e.g., 'option-chain-widget')
|
|
6920
|
+
* @returns {string} Scoped CSS for loading overlay
|
|
6921
|
+
*/
|
|
6922
|
+
const getLoadingOverlayStyles = widgetClass => `
|
|
6923
|
+
/* Loading Overlay for ${widgetClass} */
|
|
6924
|
+
.${widgetClass} .widget-loading-overlay {
|
|
6925
|
+
position: absolute;
|
|
6926
|
+
top: 0;
|
|
6927
|
+
left: 0;
|
|
6928
|
+
right: 0;
|
|
6929
|
+
bottom: 0;
|
|
6930
|
+
background: rgba(255, 255, 255, 0.95);
|
|
6931
|
+
backdrop-filter: blur(2px);
|
|
6932
|
+
display: flex;
|
|
6933
|
+
flex-direction: column;
|
|
6934
|
+
align-items: center;
|
|
6935
|
+
justify-content: center;
|
|
6936
|
+
z-index: var(--mdas-z-overlay, 100);
|
|
6937
|
+
border-radius: var(--mdas-border-radius, 12px);
|
|
6938
|
+
}
|
|
6939
|
+
|
|
6940
|
+
.${widgetClass} .widget-loading-overlay.hidden {
|
|
6941
|
+
display: none;
|
|
6942
|
+
}
|
|
6943
|
+
|
|
6944
|
+
.${widgetClass} .loading-content {
|
|
6945
|
+
display: flex;
|
|
6946
|
+
flex-direction: column;
|
|
6947
|
+
align-items: center;
|
|
6948
|
+
gap: 12px;
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
.${widgetClass} .loading-spinner {
|
|
6952
|
+
width: 40px;
|
|
6953
|
+
height: 40px;
|
|
6954
|
+
border: 4px solid var(--mdas-border-primary, #e5e7eb);
|
|
6955
|
+
border-top-color: var(--mdas-primary-color, #3b82f6);
|
|
6956
|
+
border-radius: 50%;
|
|
6957
|
+
animation: ${widgetClass}-spin 1s linear infinite;
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
.${widgetClass} .loading-text {
|
|
6961
|
+
color: var(--mdas-text-secondary, #6b7280);
|
|
6962
|
+
font-size: var(--mdas-medium-text-size, 0.93em);
|
|
6963
|
+
font-weight: 500;
|
|
6964
|
+
}
|
|
6965
|
+
|
|
6966
|
+
@keyframes ${widgetClass}-spin {
|
|
6967
|
+
to { transform: rotate(360deg); }
|
|
6968
|
+
}
|
|
6969
|
+
`;
|
|
6970
|
+
|
|
6971
|
+
/**
|
|
6972
|
+
* Generate widget error display styles
|
|
6973
|
+
* @param {string} widgetClass - Widget class name
|
|
6974
|
+
* @returns {string} Scoped CSS for error display
|
|
6975
|
+
*/
|
|
6976
|
+
const getErrorStyles = widgetClass => `
|
|
6977
|
+
/* Error Display for ${widgetClass} */
|
|
6978
|
+
.${widgetClass} .widget-error {
|
|
6979
|
+
padding: 16px 20px;
|
|
6980
|
+
background: var(--mdas-color-negative-bg, #fee2e2);
|
|
6981
|
+
border: 1px solid #fecaca;
|
|
6982
|
+
border-radius: var(--mdas-border-radius, 8px);
|
|
6983
|
+
color: var(--mdas-color-negative, #dc2626);
|
|
6984
|
+
font-size: var(--mdas-medium-text-size, 0.93em);
|
|
6985
|
+
margin: 16px;
|
|
6986
|
+
text-align: center;
|
|
6987
|
+
}
|
|
6988
|
+
|
|
6989
|
+
.${widgetClass} .widget-error-container {
|
|
6990
|
+
display: flex;
|
|
6991
|
+
flex-direction: column;
|
|
6992
|
+
align-items: center;
|
|
6993
|
+
gap: 12px;
|
|
6994
|
+
padding: 24px;
|
|
6995
|
+
}
|
|
6996
|
+
`;
|
|
6997
|
+
|
|
6998
|
+
/**
|
|
6999
|
+
* Generate widget footer styles
|
|
7000
|
+
* @param {string} widgetClass - Widget class name
|
|
7001
|
+
* @returns {string} Scoped CSS for widget footer
|
|
7002
|
+
*/
|
|
7003
|
+
const getFooterStyles = widgetClass => `
|
|
7004
|
+
/* Footer for ${widgetClass} */
|
|
7005
|
+
.${widgetClass} .widget-footer {
|
|
7006
|
+
display: flex;
|
|
7007
|
+
justify-content: space-between;
|
|
7008
|
+
align-items: center;
|
|
7009
|
+
padding: 8px 16px;
|
|
7010
|
+
background: var(--mdas-bg-secondary, #f9fafb);
|
|
7011
|
+
border-top: 1px solid var(--mdas-border-primary, #e5e7eb);
|
|
7012
|
+
font-size: var(--mdas-footer-size, 0.79em);
|
|
7013
|
+
color: var(--mdas-text-secondary, #6b7280);
|
|
7014
|
+
}
|
|
7015
|
+
|
|
7016
|
+
.${widgetClass} .widget-footer .last-update {
|
|
7017
|
+
color: var(--mdas-text-tertiary, #9ca3af);
|
|
7018
|
+
}
|
|
7019
|
+
|
|
7020
|
+
.${widgetClass} .widget-footer .data-source {
|
|
7021
|
+
color: var(--mdas-text-secondary, #6b7280);
|
|
7022
|
+
font-weight: 500;
|
|
7023
|
+
}
|
|
7024
|
+
`;
|
|
7025
|
+
|
|
7026
|
+
/**
|
|
7027
|
+
* Generate no-data state styles
|
|
7028
|
+
* @param {string} widgetClass - Widget class name
|
|
7029
|
+
* @returns {string} Scoped CSS for no-data state
|
|
7030
|
+
*/
|
|
7031
|
+
const getNoDataStyles = widgetClass => `
|
|
7032
|
+
/* No Data State for ${widgetClass} */
|
|
7033
|
+
.${widgetClass} .no-data-state {
|
|
7034
|
+
display: flex;
|
|
7035
|
+
flex-direction: column;
|
|
7036
|
+
align-items: center;
|
|
7037
|
+
justify-content: center;
|
|
7038
|
+
padding: 40px 20px;
|
|
7039
|
+
text-align: center;
|
|
7040
|
+
color: var(--mdas-text-secondary, #6b7280);
|
|
7041
|
+
}
|
|
7042
|
+
|
|
7043
|
+
.${widgetClass} .no-data-content {
|
|
7044
|
+
max-width: 400px;
|
|
7045
|
+
display: flex;
|
|
7046
|
+
flex-direction: column;
|
|
7047
|
+
align-items: center;
|
|
7048
|
+
gap: 16px;
|
|
7049
|
+
}
|
|
7050
|
+
|
|
7051
|
+
.${widgetClass} .no-data-icon {
|
|
7052
|
+
width: 64px;
|
|
7053
|
+
height: 64px;
|
|
7054
|
+
display: flex;
|
|
7055
|
+
align-items: center;
|
|
7056
|
+
justify-content: center;
|
|
7057
|
+
opacity: 0.5;
|
|
7058
|
+
}
|
|
7059
|
+
|
|
7060
|
+
.${widgetClass} .no-data-icon svg {
|
|
7061
|
+
width: 100%;
|
|
7062
|
+
height: 100%;
|
|
7063
|
+
}
|
|
7064
|
+
|
|
7065
|
+
.${widgetClass} .no-data-title {
|
|
7066
|
+
font-size: var(--mdas-large-text-size, 1.14em);
|
|
7067
|
+
font-weight: 600;
|
|
7068
|
+
color: var(--mdas-text-primary, #111827);
|
|
7069
|
+
margin: 0;
|
|
7070
|
+
}
|
|
7071
|
+
|
|
7072
|
+
.${widgetClass} .no-data-description {
|
|
7073
|
+
font-size: var(--mdas-medium-text-size, 0.93em);
|
|
7074
|
+
color: var(--mdas-text-secondary, #6b7280);
|
|
7075
|
+
margin: 0;
|
|
7076
|
+
line-height: 1.5;
|
|
7077
|
+
}
|
|
7078
|
+
|
|
7079
|
+
.${widgetClass} .no-data-description strong {
|
|
7080
|
+
color: var(--mdas-text-primary, #111827);
|
|
7081
|
+
font-weight: 600;
|
|
7082
|
+
}
|
|
7083
|
+
|
|
7084
|
+
.${widgetClass} .no-data-guidance {
|
|
7085
|
+
font-size: var(--mdas-small-text-size, 0.79em);
|
|
7086
|
+
color: var(--mdas-text-tertiary, #9ca3af);
|
|
7087
|
+
margin: 0;
|
|
7088
|
+
}
|
|
7089
|
+
|
|
7090
|
+
/* Error variant of no-data state */
|
|
7091
|
+
.${widgetClass} .no-data-state.error-access {
|
|
7092
|
+
color: var(--mdas-color-negative, #dc2626);
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
.${widgetClass} .no-data-state.error-access .no-data-title {
|
|
7096
|
+
color: var(--mdas-color-negative, #dc2626);
|
|
7097
|
+
}
|
|
7098
|
+
|
|
7099
|
+
.${widgetClass} .no-data-state.error-access .no-data-icon {
|
|
7100
|
+
opacity: 0.8;
|
|
7101
|
+
}
|
|
7102
|
+
|
|
7103
|
+
.${widgetClass} .no-data-state.error-access .no-data-icon svg circle[fill] {
|
|
7104
|
+
fill: var(--mdas-color-negative, #dc2626);
|
|
7105
|
+
}
|
|
7106
|
+
`;
|
|
7107
|
+
|
|
6703
7108
|
// src/widgets/styles/OptionChainStyles.js
|
|
6704
7109
|
const OptionChainStyles = `
|
|
6705
|
-
${
|
|
7110
|
+
${BaseStyles}
|
|
7111
|
+
${getLoadingOverlayStyles('option-chain-widget')}
|
|
7112
|
+
${getErrorStyles('option-chain-widget')}
|
|
7113
|
+
${getFooterStyles('option-chain-widget')}
|
|
7114
|
+
${getNoDataStyles('option-chain-widget')}
|
|
6706
7115
|
|
|
6707
|
-
/*
|
|
7116
|
+
/* Option Chain Widget Specific Styles */
|
|
6708
7117
|
.option-chain-widget {
|
|
6709
7118
|
border: 1px solid #e5e7eb;
|
|
6710
7119
|
border-radius: 8px;
|
|
@@ -6729,8 +7138,22 @@ ${SharedStyles}
|
|
|
6729
7138
|
flex-wrap: wrap;
|
|
6730
7139
|
}
|
|
6731
7140
|
|
|
7141
|
+
.option-chain-widget .filter-section {
|
|
7142
|
+
display: flex;
|
|
7143
|
+
gap: 8px;
|
|
7144
|
+
align-items: center;
|
|
7145
|
+
margin-top: 8px;
|
|
7146
|
+
}
|
|
7147
|
+
|
|
7148
|
+
.option-chain-widget .filter-label {
|
|
7149
|
+
font-size: 13px;
|
|
7150
|
+
font-weight: 500;
|
|
7151
|
+
color: #374151;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
6732
7154
|
.option-chain-widget .symbol-input,
|
|
6733
|
-
.option-chain-widget .date-select
|
|
7155
|
+
.option-chain-widget .date-select,
|
|
7156
|
+
.option-chain-widget .strike-filter {
|
|
6734
7157
|
padding: 8px 12px;
|
|
6735
7158
|
border: 1px solid #d1d5db;
|
|
6736
7159
|
border-radius: 4px;
|
|
@@ -6751,6 +7174,12 @@ ${SharedStyles}
|
|
|
6751
7174
|
background: white;
|
|
6752
7175
|
}
|
|
6753
7176
|
|
|
7177
|
+
.option-chain-widget .strike-filter {
|
|
7178
|
+
min-width: 200px;
|
|
7179
|
+
background: white;
|
|
7180
|
+
cursor: pointer;
|
|
7181
|
+
}
|
|
7182
|
+
|
|
6754
7183
|
.option-chain-widget .fetch-button {
|
|
6755
7184
|
padding: 8px 16px;
|
|
6756
7185
|
background: #3b82f6;
|
|
@@ -6906,10 +7335,22 @@ ${SharedStyles}
|
|
|
6906
7335
|
min-height: 32px;
|
|
6907
7336
|
}
|
|
6908
7337
|
|
|
7338
|
+
/* In-the-money highlighting */
|
|
7339
|
+
.option-chain-widget .calls-data.in-the-money span,
|
|
7340
|
+
.option-chain-widget .puts-data.in-the-money span {
|
|
7341
|
+
background-color: #fffbeb;
|
|
7342
|
+
}
|
|
7343
|
+
|
|
6909
7344
|
.option-chain-widget .option-row:hover {
|
|
6910
7345
|
background: #f9fafb;
|
|
6911
7346
|
}
|
|
6912
|
-
|
|
7347
|
+
|
|
7348
|
+
.option-chain-widget .option-row:hover .calls-data.in-the-money span,
|
|
7349
|
+
.option-chain-widget .option-row:hover .puts-data.in-the-money span {
|
|
7350
|
+
background-color: #fef3c7;
|
|
7351
|
+
}
|
|
7352
|
+
|
|
7353
|
+
.option-chain-widget .option-row:hover .calls-data span,
|
|
6913
7354
|
.option-chain-widget .option-row:hover .puts-data span,
|
|
6914
7355
|
.option-chain-widget .option-row:hover .strike-data {
|
|
6915
7356
|
background: #f9fafb;
|
|
@@ -6933,14 +7374,32 @@ ${SharedStyles}
|
|
|
6933
7374
|
.option-chain-widget .puts-data span {
|
|
6934
7375
|
padding: 6px 4px;
|
|
6935
7376
|
text-align: center;
|
|
6936
|
-
font-size: 12px;
|
|
6937
|
-
font-weight: 400;
|
|
6938
|
-
color: #111827;
|
|
7377
|
+
font-size: 12px;
|
|
7378
|
+
font-weight: 400;
|
|
7379
|
+
color: #111827;
|
|
6939
7380
|
overflow: hidden;
|
|
6940
7381
|
text-overflow: ellipsis;
|
|
6941
7382
|
white-space: nowrap;
|
|
6942
7383
|
}
|
|
6943
7384
|
|
|
7385
|
+
/* Override color for change values with positive/negative classes */
|
|
7386
|
+
.option-chain-widget .calls-data span.positive,
|
|
7387
|
+
.option-chain-widget .puts-data span.positive {
|
|
7388
|
+
color: #059669 !important;
|
|
7389
|
+
font-weight: 500;
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
.option-chain-widget .calls-data span.negative,
|
|
7393
|
+
.option-chain-widget .puts-data span.negative {
|
|
7394
|
+
color: #dc2626 !important;
|
|
7395
|
+
font-weight: 500;
|
|
7396
|
+
}
|
|
7397
|
+
|
|
7398
|
+
.option-chain-widget .calls-data span.neutral,
|
|
7399
|
+
.option-chain-widget .puts-data span.neutral {
|
|
7400
|
+
color: #6b7280 !important;
|
|
7401
|
+
}
|
|
7402
|
+
|
|
6944
7403
|
.option-chain-widget .contract-cell {
|
|
6945
7404
|
font-size: 10px;
|
|
6946
7405
|
color: #6b7280;
|
|
@@ -6952,78 +7411,42 @@ ${SharedStyles}
|
|
|
6952
7411
|
white-space: nowrap;
|
|
6953
7412
|
}
|
|
6954
7413
|
|
|
6955
|
-
.option-chain-widget .
|
|
6956
|
-
color: #
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
color: #dc2626;
|
|
6961
|
-
}
|
|
6962
|
-
|
|
6963
|
-
.option-chain-widget .neutral {
|
|
6964
|
-
color: #6b7280;
|
|
6965
|
-
}
|
|
6966
|
-
|
|
6967
|
-
.option-chain-widget .widget-footer {
|
|
6968
|
-
background: #f9fafb;
|
|
6969
|
-
padding: 8px 16px;
|
|
6970
|
-
border-top: 1px solid #e5e7eb;
|
|
6971
|
-
text-align: center;
|
|
6972
|
-
font-size: 11px;
|
|
6973
|
-
color: #6b7280;
|
|
7414
|
+
.option-chain-widget .contract-cell.clickable {
|
|
7415
|
+
color: #3b82f6;
|
|
7416
|
+
cursor: pointer;
|
|
7417
|
+
text-decoration: underline;
|
|
7418
|
+
transition: all 0.2s ease;
|
|
6974
7419
|
}
|
|
6975
7420
|
|
|
6976
|
-
.option-chain-widget .
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
left: 0;
|
|
6980
|
-
right: 0;
|
|
6981
|
-
bottom: 0;
|
|
6982
|
-
background: rgba(255, 255, 255, 0.9);
|
|
6983
|
-
display: flex;
|
|
6984
|
-
align-items: center;
|
|
6985
|
-
justify-content: center;
|
|
6986
|
-
z-index: 20;
|
|
7421
|
+
.option-chain-widget .contract-cell.clickable:hover {
|
|
7422
|
+
color: #2563eb;
|
|
7423
|
+
background-color: #eff6ff;
|
|
6987
7424
|
}
|
|
6988
7425
|
|
|
6989
|
-
.option-chain-widget .
|
|
6990
|
-
|
|
7426
|
+
.option-chain-widget .contract-cell.clickable:focus {
|
|
7427
|
+
outline: 2px solid #3b82f6;
|
|
7428
|
+
outline-offset: 2px;
|
|
7429
|
+
border-radius: 2px;
|
|
6991
7430
|
}
|
|
6992
7431
|
|
|
6993
|
-
.option-chain-widget .
|
|
6994
|
-
|
|
7432
|
+
.option-chain-widget .contract-cell.clickable:active {
|
|
7433
|
+
color: #1e40af;
|
|
7434
|
+
background-color: #dbeafe;
|
|
6995
7435
|
}
|
|
6996
7436
|
|
|
6997
|
-
.option-chain-widget .
|
|
6998
|
-
|
|
6999
|
-
height: 32px;
|
|
7000
|
-
border: 3px solid #e5e7eb;
|
|
7001
|
-
border-top: 3px solid #3b82f6;
|
|
7002
|
-
border-radius: 50%;
|
|
7003
|
-
animation: spin 1s linear infinite;
|
|
7004
|
-
margin: 0 auto 12px;
|
|
7437
|
+
.option-chain-widget .positive {
|
|
7438
|
+
color: #059669;
|
|
7005
7439
|
}
|
|
7006
7440
|
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
100% { transform: rotate(360deg); }
|
|
7441
|
+
.option-chain-widget .negative {
|
|
7442
|
+
color: #dc2626;
|
|
7010
7443
|
}
|
|
7011
7444
|
|
|
7012
|
-
.option-chain-widget .
|
|
7013
|
-
padding: 40px;
|
|
7014
|
-
text-align: center;
|
|
7445
|
+
.option-chain-widget .neutral {
|
|
7015
7446
|
color: #6b7280;
|
|
7016
7447
|
}
|
|
7017
7448
|
|
|
7018
|
-
|
|
7019
|
-
padding: 20px;
|
|
7020
|
-
background: #fef2f2;
|
|
7021
|
-
color: #dc2626;
|
|
7022
|
-
border: 1px solid #fecaca;
|
|
7023
|
-
margin: 16px;
|
|
7024
|
-
border-radius: 4px;
|
|
7025
|
-
text-align: center;
|
|
7026
|
-
}
|
|
7449
|
+
/* Loading, Error, Footer, and No-Data styles provided by CommonWidgetPatterns */
|
|
7027
7450
|
|
|
7028
7451
|
/* RESPONSIVE STYLES */
|
|
7029
7452
|
|
|
@@ -7034,13 +7457,19 @@ ${SharedStyles}
|
|
|
7034
7457
|
align-items: stretch;
|
|
7035
7458
|
gap: 8px;
|
|
7036
7459
|
}
|
|
7037
|
-
|
|
7460
|
+
|
|
7461
|
+
.option-chain-widget .filter-section {
|
|
7462
|
+
flex-direction: row;
|
|
7463
|
+
margin-top: 0;
|
|
7464
|
+
}
|
|
7465
|
+
|
|
7038
7466
|
.option-chain-widget .symbol-input,
|
|
7039
|
-
.option-chain-widget .date-select
|
|
7467
|
+
.option-chain-widget .date-select,
|
|
7468
|
+
.option-chain-widget .strike-filter {
|
|
7040
7469
|
width: 100%;
|
|
7041
7470
|
max-width: none;
|
|
7042
7471
|
}
|
|
7043
|
-
|
|
7472
|
+
|
|
7044
7473
|
.option-chain-widget .fetch-button {
|
|
7045
7474
|
width: 100%;
|
|
7046
7475
|
}
|
|
@@ -7196,6 +7625,121 @@ ${SharedStyles}
|
|
|
7196
7625
|
padding: 2px 1px;
|
|
7197
7626
|
}
|
|
7198
7627
|
}
|
|
7628
|
+
|
|
7629
|
+
/* Modal Styles */
|
|
7630
|
+
.option-chain-modal-overlay {
|
|
7631
|
+
position: fixed;
|
|
7632
|
+
top: 0;
|
|
7633
|
+
left: 0;
|
|
7634
|
+
right: 0;
|
|
7635
|
+
bottom: 0;
|
|
7636
|
+
background: rgba(0, 0, 0, 0.6);
|
|
7637
|
+
display: flex;
|
|
7638
|
+
align-items: center;
|
|
7639
|
+
justify-content: center;
|
|
7640
|
+
z-index: 10000;
|
|
7641
|
+
padding: 20px;
|
|
7642
|
+
animation: fadeIn 0.2s ease-out;
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7645
|
+
@keyframes fadeIn {
|
|
7646
|
+
from {
|
|
7647
|
+
opacity: 0;
|
|
7648
|
+
}
|
|
7649
|
+
to {
|
|
7650
|
+
opacity: 1;
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
|
|
7654
|
+
.option-chain-modal {
|
|
7655
|
+
background: white;
|
|
7656
|
+
border-radius: 12px;
|
|
7657
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
7658
|
+
max-width: 800px;
|
|
7659
|
+
width: 100%;
|
|
7660
|
+
max-height: 90vh;
|
|
7661
|
+
display: flex;
|
|
7662
|
+
flex-direction: column;
|
|
7663
|
+
animation: slideUp 0.3s ease-out;
|
|
7664
|
+
}
|
|
7665
|
+
|
|
7666
|
+
@keyframes slideUp {
|
|
7667
|
+
from {
|
|
7668
|
+
transform: translateY(20px);
|
|
7669
|
+
opacity: 0;
|
|
7670
|
+
}
|
|
7671
|
+
to {
|
|
7672
|
+
transform: translateY(0);
|
|
7673
|
+
opacity: 1;
|
|
7674
|
+
}
|
|
7675
|
+
}
|
|
7676
|
+
|
|
7677
|
+
.option-chain-modal-header {
|
|
7678
|
+
display: flex;
|
|
7679
|
+
align-items: center;
|
|
7680
|
+
justify-content: space-between;
|
|
7681
|
+
padding: 20px 24px;
|
|
7682
|
+
border-bottom: 1px solid #e5e7eb;
|
|
7683
|
+
}
|
|
7684
|
+
|
|
7685
|
+
.option-chain-modal-header h3 {
|
|
7686
|
+
margin: 0;
|
|
7687
|
+
font-size: 18px;
|
|
7688
|
+
font-weight: 600;
|
|
7689
|
+
color: #111827;
|
|
7690
|
+
}
|
|
7691
|
+
|
|
7692
|
+
.option-chain-modal-close {
|
|
7693
|
+
background: none;
|
|
7694
|
+
border: none;
|
|
7695
|
+
font-size: 28px;
|
|
7696
|
+
line-height: 1;
|
|
7697
|
+
color: #6b7280;
|
|
7698
|
+
cursor: pointer;
|
|
7699
|
+
padding: 0;
|
|
7700
|
+
width: 32px;
|
|
7701
|
+
height: 32px;
|
|
7702
|
+
display: flex;
|
|
7703
|
+
align-items: center;
|
|
7704
|
+
justify-content: center;
|
|
7705
|
+
border-radius: 4px;
|
|
7706
|
+
transition: all 0.2s ease;
|
|
7707
|
+
}
|
|
7708
|
+
|
|
7709
|
+
.option-chain-modal-close:hover {
|
|
7710
|
+
background: #f3f4f6;
|
|
7711
|
+
color: #111827;
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7714
|
+
.option-chain-modal-close:active {
|
|
7715
|
+
background: #e5e7eb;
|
|
7716
|
+
}
|
|
7717
|
+
|
|
7718
|
+
.option-chain-modal-body {
|
|
7719
|
+
flex: 1;
|
|
7720
|
+
overflow-y: auto;
|
|
7721
|
+
padding: 24px;
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
/* Responsive modal */
|
|
7725
|
+
@media (max-width: 768px) {
|
|
7726
|
+
.option-chain-modal {
|
|
7727
|
+
max-width: 95%;
|
|
7728
|
+
max-height: 95vh;
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7731
|
+
.option-chain-modal-header {
|
|
7732
|
+
padding: 16px;
|
|
7733
|
+
}
|
|
7734
|
+
|
|
7735
|
+
.option-chain-modal-header h3 {
|
|
7736
|
+
font-size: 16px;
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
.option-chain-modal-body {
|
|
7740
|
+
padding: 16px;
|
|
7741
|
+
}
|
|
7742
|
+
}
|
|
7199
7743
|
`;
|
|
7200
7744
|
|
|
7201
7745
|
class OptionChainWidget extends BaseWidget {
|
|
@@ -7210,7 +7754,18 @@ ${SharedStyles}
|
|
|
7210
7754
|
this.data = null;
|
|
7211
7755
|
this.isDestroyed = false;
|
|
7212
7756
|
this.unsubscribe = null;
|
|
7757
|
+
this.unsubscribeUnderlying = null; // For underlying stock price
|
|
7758
|
+
this.underlyingPrice = null; // Current price of underlying stock
|
|
7213
7759
|
this.loadingTimeout = null;
|
|
7760
|
+
this.renderDebounceTimeout = null; // For debouncing re-renders
|
|
7761
|
+
|
|
7762
|
+
// Separate cache for option chain data and underlying price
|
|
7763
|
+
this.cachedOptionChainData = null;
|
|
7764
|
+
this.cachedUnderlyingPrice = null;
|
|
7765
|
+
|
|
7766
|
+
// Modal for Options widget
|
|
7767
|
+
this.optionsModal = null;
|
|
7768
|
+
this.optionsWidgetInstance = null;
|
|
7214
7769
|
|
|
7215
7770
|
// INPUT VALIDATION: Validate initial symbol if provided
|
|
7216
7771
|
if (options.symbol) {
|
|
@@ -7226,6 +7781,7 @@ ${SharedStyles}
|
|
|
7226
7781
|
}
|
|
7227
7782
|
this.date = '';
|
|
7228
7783
|
this.availableDates = {};
|
|
7784
|
+
this.strikeFilterRange = 5; // Default: show ±10 strikes from ATM
|
|
7229
7785
|
|
|
7230
7786
|
// RATE LIMITING: Create rate limiter for fetch button (1 second between fetches)
|
|
7231
7787
|
this.fetchRateLimiter = createRateLimitValidator(1000);
|
|
@@ -7256,6 +7812,7 @@ ${SharedStyles}
|
|
|
7256
7812
|
this.dateSelect = this.container.querySelector('.date-select');
|
|
7257
7813
|
this.fetchButton = this.container.querySelector('.fetch-button');
|
|
7258
7814
|
this.dataGrid = this.container.querySelector('.option-chain-data-grid');
|
|
7815
|
+
this.strikeFilterSelect = this.container.querySelector('.strike-filter');
|
|
7259
7816
|
|
|
7260
7817
|
// Set initial symbol value if provided in options
|
|
7261
7818
|
if (this.symbol) {
|
|
@@ -7330,6 +7887,22 @@ ${SharedStyles}
|
|
|
7330
7887
|
}
|
|
7331
7888
|
this.loadAvailableDates(symbolValidation.sanitized);
|
|
7332
7889
|
});
|
|
7890
|
+
|
|
7891
|
+
// MEMORY LEAK FIX: Use BaseWidget's addEventListener
|
|
7892
|
+
// Strike filter dropdown
|
|
7893
|
+
this.addEventListener(this.strikeFilterSelect, 'change', e => {
|
|
7894
|
+
const value = e.target.value;
|
|
7895
|
+
if (value === 'all') {
|
|
7896
|
+
this.strikeFilterRange = 'all';
|
|
7897
|
+
} else {
|
|
7898
|
+
this.strikeFilterRange = parseInt(value, 10);
|
|
7899
|
+
}
|
|
7900
|
+
|
|
7901
|
+
// Re-render with new filter if we have data
|
|
7902
|
+
if (this.data) {
|
|
7903
|
+
this.displayOptionChain(this.data);
|
|
7904
|
+
}
|
|
7905
|
+
});
|
|
7333
7906
|
}
|
|
7334
7907
|
async loadAvailableDates(symbol) {
|
|
7335
7908
|
if (!symbol) return;
|
|
@@ -7345,7 +7918,9 @@ ${SharedStyles}
|
|
|
7345
7918
|
// Use API service from wsManager
|
|
7346
7919
|
const apiService = this.wsManager.getApiService();
|
|
7347
7920
|
const data = await apiService.getOptionChainDates(symbol);
|
|
7348
|
-
|
|
7921
|
+
if (this.debug) {
|
|
7922
|
+
console.log('[OptionChainWidget] Available dates:', data.dates_dictionary);
|
|
7923
|
+
}
|
|
7349
7924
|
this.availableDates = data.dates_dictionary || {};
|
|
7350
7925
|
this.populateDateOptions();
|
|
7351
7926
|
|
|
@@ -7439,11 +8014,15 @@ ${SharedStyles}
|
|
|
7439
8014
|
this.showError(`No data received for ${this.symbol} on ${this.date}. Please try again.`);
|
|
7440
8015
|
}, 10000); // 10 second timeout
|
|
7441
8016
|
|
|
7442
|
-
// Unsubscribe from previous
|
|
8017
|
+
// Unsubscribe from previous subscriptions if they exist
|
|
7443
8018
|
if (this.unsubscribe) {
|
|
7444
8019
|
this.unsubscribe();
|
|
7445
8020
|
this.unsubscribe = null;
|
|
7446
8021
|
}
|
|
8022
|
+
if (this.unsubscribeUnderlying) {
|
|
8023
|
+
this.unsubscribeUnderlying();
|
|
8024
|
+
this.unsubscribeUnderlying = null;
|
|
8025
|
+
}
|
|
7447
8026
|
|
|
7448
8027
|
// Subscribe to option chain data
|
|
7449
8028
|
this.subscribeToData();
|
|
@@ -7453,25 +8032,62 @@ ${SharedStyles}
|
|
|
7453
8032
|
}
|
|
7454
8033
|
}
|
|
7455
8034
|
subscribeToData() {
|
|
7456
|
-
// Subscribe
|
|
7457
|
-
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryoptionchain'], this.handleMessage.bind(this), this.symbol,
|
|
7458
|
-
// Pass symbol as string
|
|
7459
|
-
{
|
|
8035
|
+
// Subscribe to option chain data
|
|
8036
|
+
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryoptionchain'], this.handleMessage.bind(this), this.symbol, {
|
|
7460
8037
|
date: this.date
|
|
7461
|
-
}
|
|
7462
|
-
);
|
|
8038
|
+
});
|
|
7463
8039
|
|
|
7464
|
-
//
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
8040
|
+
// Subscribe to underlying stock price for ITM highlighting
|
|
8041
|
+
this.unsubscribeUnderlying = this.wsManager.subscribe(`${this.widgetId}-underlying`, ['queryl1'], this.handleMessage.bind(this), this.symbol);
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
/**
|
|
8045
|
+
* Find the nearest strike price to the underlying price (ATM strike)
|
|
8046
|
+
* @param {Array} sortedStrikes - Array of strike prices sorted ascending
|
|
8047
|
+
* @param {Number} underlyingPrice - Current price of underlying stock
|
|
8048
|
+
* @returns {String} - The nearest strike price
|
|
8049
|
+
*/
|
|
8050
|
+
findNearestStrike(sortedStrikes, underlyingPrice) {
|
|
8051
|
+
if (!sortedStrikes || sortedStrikes.length === 0 || !underlyingPrice) {
|
|
8052
|
+
return null;
|
|
8053
|
+
}
|
|
8054
|
+
|
|
8055
|
+
// Find strike closest to underlying price
|
|
8056
|
+
let nearestStrike = sortedStrikes[0];
|
|
8057
|
+
let minDifference = Math.abs(parseFloat(sortedStrikes[0]) - underlyingPrice);
|
|
8058
|
+
for (const strike of sortedStrikes) {
|
|
8059
|
+
const difference = Math.abs(parseFloat(strike) - underlyingPrice);
|
|
8060
|
+
if (difference < minDifference) {
|
|
8061
|
+
minDifference = difference;
|
|
8062
|
+
nearestStrike = strike;
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
return nearestStrike;
|
|
8066
|
+
}
|
|
8067
|
+
|
|
8068
|
+
/**
|
|
8069
|
+
* Filter strikes to show only those near the money
|
|
8070
|
+
* @param {Array} sortedStrikes - Array of strike prices sorted ascending
|
|
8071
|
+
* @param {String} atmStrike - The at-the-money strike price
|
|
8072
|
+
* @param {Number} range - Number of strikes to show above and below ATM
|
|
8073
|
+
* @returns {Array} - Filtered array of strikes
|
|
8074
|
+
*/
|
|
8075
|
+
filterNearMoneyStrikes(sortedStrikes, atmStrike, range) {
|
|
8076
|
+
if (!atmStrike || !sortedStrikes || sortedStrikes.length === 0) {
|
|
8077
|
+
return sortedStrikes;
|
|
8078
|
+
}
|
|
8079
|
+
const atmIndex = sortedStrikes.indexOf(atmStrike);
|
|
8080
|
+
if (atmIndex === -1) {
|
|
8081
|
+
return sortedStrikes;
|
|
8082
|
+
}
|
|
8083
|
+
const startIndex = Math.max(0, atmIndex - range);
|
|
8084
|
+
const endIndex = Math.min(sortedStrikes.length - 1, atmIndex + range);
|
|
8085
|
+
return sortedStrikes.slice(startIndex, endIndex + 1);
|
|
7470
8086
|
}
|
|
7471
8087
|
handleData(message) {
|
|
7472
|
-
// Clear loading timeout since we received data
|
|
8088
|
+
// Clear loading timeout since we received data (use BaseWidget method)
|
|
7473
8089
|
if (this.loadingTimeout) {
|
|
7474
|
-
clearTimeout(this.loadingTimeout);
|
|
8090
|
+
this.clearTimeout(this.loadingTimeout);
|
|
7475
8091
|
this.loadingTimeout = null;
|
|
7476
8092
|
}
|
|
7477
8093
|
|
|
@@ -7503,7 +8119,9 @@ ${SharedStyles}
|
|
|
7503
8119
|
}
|
|
7504
8120
|
}
|
|
7505
8121
|
} else {
|
|
7506
|
-
|
|
8122
|
+
// Cache the option chain data
|
|
8123
|
+
this.data = message;
|
|
8124
|
+
this.cachedOptionChainData = message;
|
|
7507
8125
|
this.displayOptionChain(message);
|
|
7508
8126
|
}
|
|
7509
8127
|
} else if (message.type === 'queryoptionchain' && Array.isArray(message.data)) {
|
|
@@ -7518,9 +8136,35 @@ ${SharedStyles}
|
|
|
7518
8136
|
}
|
|
7519
8137
|
}
|
|
7520
8138
|
} else {
|
|
7521
|
-
|
|
8139
|
+
// Cache the option chain data
|
|
8140
|
+
this.data = message.data;
|
|
8141
|
+
this.cachedOptionChainData = message.data;
|
|
7522
8142
|
this.displayOptionChain(message.data);
|
|
7523
8143
|
}
|
|
8144
|
+
} else if (message.type === 'queryl1' && message.Data) {
|
|
8145
|
+
if (this.debug) {
|
|
8146
|
+
console.log('[OptionChainWidget] Received underlying price update');
|
|
8147
|
+
}
|
|
8148
|
+
const data = message.Data.find(d => d.Symbol === this.symbol);
|
|
8149
|
+
if (data && data.LastPx) {
|
|
8150
|
+
// Cache the underlying price
|
|
8151
|
+
this.underlyingPrice = parseFloat(data.LastPx);
|
|
8152
|
+
this.cachedUnderlyingPrice = parseFloat(data.LastPx);
|
|
8153
|
+
|
|
8154
|
+
// Debounce re-render to prevent excessive updates
|
|
8155
|
+
// Clear any pending render
|
|
8156
|
+
if (this.renderDebounceTimeout) {
|
|
8157
|
+
this.clearTimeout(this.renderDebounceTimeout);
|
|
8158
|
+
}
|
|
8159
|
+
|
|
8160
|
+
// Schedule a debounced re-render (500ms delay)
|
|
8161
|
+
this.renderDebounceTimeout = this.setTimeout(() => {
|
|
8162
|
+
if (this.data) {
|
|
8163
|
+
this.displayOptionChain(this.data);
|
|
8164
|
+
}
|
|
8165
|
+
this.renderDebounceTimeout = null;
|
|
8166
|
+
}, 500);
|
|
8167
|
+
}
|
|
7524
8168
|
}
|
|
7525
8169
|
if (message._cached) {
|
|
7526
8170
|
this.showConnectionQuality(); // Show cache indicator
|
|
@@ -7528,6 +8172,14 @@ ${SharedStyles}
|
|
|
7528
8172
|
}
|
|
7529
8173
|
displayOptionChain(data) {
|
|
7530
8174
|
if (this.isDestroyed) return;
|
|
8175
|
+
|
|
8176
|
+
// Validate data
|
|
8177
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
8178
|
+
if (this.debug) {
|
|
8179
|
+
console.warn('[OptionChainWidget] Invalid or empty data passed to displayOptionChain');
|
|
8180
|
+
}
|
|
8181
|
+
return;
|
|
8182
|
+
}
|
|
7531
8183
|
try {
|
|
7532
8184
|
// Hide loading overlay
|
|
7533
8185
|
this.hideLoading();
|
|
@@ -7560,9 +8212,18 @@ ${SharedStyles}
|
|
|
7560
8212
|
}
|
|
7561
8213
|
});
|
|
7562
8214
|
|
|
7563
|
-
// Sort strikes
|
|
8215
|
+
// Sort strikes
|
|
7564
8216
|
const sortedStrikes = Object.keys(optionsByStrike).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
7565
|
-
|
|
8217
|
+
|
|
8218
|
+
// Apply ATM filter if not showing all
|
|
8219
|
+
let displayStrikes = sortedStrikes;
|
|
8220
|
+
if (this.strikeFilterRange !== 'all' && this.underlyingPrice) {
|
|
8221
|
+
const atmStrike = this.findNearestStrike(sortedStrikes, this.underlyingPrice);
|
|
8222
|
+
if (atmStrike) {
|
|
8223
|
+
displayStrikes = this.filterNearMoneyStrikes(sortedStrikes, atmStrike, this.strikeFilterRange);
|
|
8224
|
+
}
|
|
8225
|
+
}
|
|
8226
|
+
displayStrikes.forEach(strike => {
|
|
7566
8227
|
const {
|
|
7567
8228
|
call,
|
|
7568
8229
|
put
|
|
@@ -7581,7 +8242,7 @@ ${SharedStyles}
|
|
|
7581
8242
|
const formatted = parseFloat(change).toFixed(2);
|
|
7582
8243
|
const className = change > 0 ? 'positive' : change < 0 ? 'negative' : 'neutral';
|
|
7583
8244
|
const sign = change > 0 ? '+' : '';
|
|
7584
|
-
span.className =
|
|
8245
|
+
span.className = className;
|
|
7585
8246
|
span.textContent = `${sign}${formatted}`;
|
|
7586
8247
|
return span;
|
|
7587
8248
|
};
|
|
@@ -7594,7 +8255,28 @@ ${SharedStyles}
|
|
|
7594
8255
|
// Create calls data section
|
|
7595
8256
|
const callsData = document.createElement('div');
|
|
7596
8257
|
callsData.className = 'calls-data';
|
|
7597
|
-
|
|
8258
|
+
|
|
8259
|
+
// Add ITM highlighting for calls (ITM when strike < underlying price)
|
|
8260
|
+
if (this.underlyingPrice && parseFloat(strike) < this.underlyingPrice) {
|
|
8261
|
+
callsData.classList.add('in-the-money');
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
// Make contract cell clickable
|
|
8265
|
+
const callContractCell = createElement('span', call ? sanitizeSymbol(call.symbol) : '--', 'contract-cell');
|
|
8266
|
+
if (call && call.symbol) {
|
|
8267
|
+
callContractCell.classList.add('clickable');
|
|
8268
|
+
callContractCell.setAttribute('role', 'button');
|
|
8269
|
+
callContractCell.setAttribute('tabindex', '0');
|
|
8270
|
+
callContractCell.setAttribute('data-symbol', call.symbol);
|
|
8271
|
+
this.addEventListener(callContractCell, 'click', () => this.showOptionsModal(call.symbol));
|
|
8272
|
+
this.addEventListener(callContractCell, 'keypress', e => {
|
|
8273
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
8274
|
+
e.preventDefault();
|
|
8275
|
+
this.showOptionsModal(call.symbol);
|
|
8276
|
+
}
|
|
8277
|
+
});
|
|
8278
|
+
}
|
|
8279
|
+
callsData.appendChild(callContractCell);
|
|
7598
8280
|
callsData.appendChild(createElement('span', formatPrice(call?.lastPrice)));
|
|
7599
8281
|
const callChangeSpan = document.createElement('span');
|
|
7600
8282
|
if (call) {
|
|
@@ -7617,7 +8299,28 @@ ${SharedStyles}
|
|
|
7617
8299
|
// Create puts data section
|
|
7618
8300
|
const putsData = document.createElement('div');
|
|
7619
8301
|
putsData.className = 'puts-data';
|
|
7620
|
-
|
|
8302
|
+
|
|
8303
|
+
// Add ITM highlighting for puts (ITM when strike > underlying price)
|
|
8304
|
+
if (this.underlyingPrice && parseFloat(strike) > this.underlyingPrice) {
|
|
8305
|
+
putsData.classList.add('in-the-money');
|
|
8306
|
+
}
|
|
8307
|
+
|
|
8308
|
+
// Make contract cell clickable
|
|
8309
|
+
const putContractCell = createElement('span', put ? sanitizeSymbol(put.symbol) : '', 'contract-cell');
|
|
8310
|
+
if (put && put.symbol) {
|
|
8311
|
+
putContractCell.classList.add('clickable');
|
|
8312
|
+
putContractCell.setAttribute('role', 'button');
|
|
8313
|
+
putContractCell.setAttribute('tabindex', '0');
|
|
8314
|
+
putContractCell.setAttribute('data-symbol', put.symbol);
|
|
8315
|
+
this.addEventListener(putContractCell, 'click', () => this.showOptionsModal(put.symbol));
|
|
8316
|
+
this.addEventListener(putContractCell, 'keypress', e => {
|
|
8317
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
8318
|
+
e.preventDefault();
|
|
8319
|
+
this.showOptionsModal(put.symbol);
|
|
8320
|
+
}
|
|
8321
|
+
});
|
|
8322
|
+
}
|
|
8323
|
+
putsData.appendChild(putContractCell);
|
|
7621
8324
|
putsData.appendChild(createElement('span', formatPrice(put?.lastPrice)));
|
|
7622
8325
|
const putChangeSpan = document.createElement('span');
|
|
7623
8326
|
if (put) {
|
|
@@ -7641,14 +8344,22 @@ ${SharedStyles}
|
|
|
7641
8344
|
|
|
7642
8345
|
// Add footer - with array length check
|
|
7643
8346
|
if (data && data.length > 0) {
|
|
7644
|
-
const
|
|
8347
|
+
const isDelayed = data[0].DataSource === 'OPRA-D';
|
|
8348
|
+
|
|
8349
|
+
// Get timestamp - subtract 20 minutes if delayed data
|
|
8350
|
+
let timestampValue = data[0].quoteTime || Date.now();
|
|
8351
|
+
if (isDelayed) {
|
|
8352
|
+
// Subtract 20 minutes (20 * 60 * 1000 milliseconds)
|
|
8353
|
+
timestampValue = timestampValue - 20 * 60 * 1000;
|
|
8354
|
+
}
|
|
8355
|
+
const timestamp = formatTimestampET(timestampValue);
|
|
7645
8356
|
const lastUpdateElement = this.container.querySelector('.last-update');
|
|
7646
8357
|
if (lastUpdateElement) {
|
|
7647
8358
|
lastUpdateElement.textContent = `Last update: ${timestamp}`;
|
|
7648
8359
|
}
|
|
7649
8360
|
|
|
7650
8361
|
// Update footer
|
|
7651
|
-
const source =
|
|
8362
|
+
const source = isDelayed ? '20 mins delayed' : 'Real-time';
|
|
7652
8363
|
const dataSourceElement = this.container.querySelector('.data-source');
|
|
7653
8364
|
if (dataSourceElement) {
|
|
7654
8365
|
dataSourceElement.textContent = `Source: ${source}`;
|
|
@@ -7671,35 +8382,6 @@ ${SharedStyles}
|
|
|
7671
8382
|
loadingOverlay.classList.add('hidden');
|
|
7672
8383
|
}
|
|
7673
8384
|
}
|
|
7674
|
-
|
|
7675
|
-
/* showInputError(message) {
|
|
7676
|
-
const errorDiv = document.createElement('div');
|
|
7677
|
-
errorDiv.className = 'symbol-error-message';
|
|
7678
|
-
errorDiv.textContent = message;
|
|
7679
|
-
errorDiv.style.cssText = `
|
|
7680
|
-
color: #dc2626;
|
|
7681
|
-
font-size: 12px;
|
|
7682
|
-
margin-bottom: 2px;
|
|
7683
|
-
position: absolute;
|
|
7684
|
-
background: white;
|
|
7685
|
-
z-index: 1000;
|
|
7686
|
-
transform: translateY(-100%);
|
|
7687
|
-
top: -4px;
|
|
7688
|
-
left: 0; // Align with input box
|
|
7689
|
-
`;
|
|
7690
|
-
|
|
7691
|
-
// Ensure the parent is positioned
|
|
7692
|
-
const parent = this.symbolInput;
|
|
7693
|
-
if (parent) {
|
|
7694
|
-
parent.style.position = 'relative';
|
|
7695
|
-
// Insert the errorDiv as the first child of the parent
|
|
7696
|
-
parent.insertBefore(errorDiv, parent.firstChild);
|
|
7697
|
-
}
|
|
7698
|
-
|
|
7699
|
-
this.symbolInput.errorMessage = errorDiv;
|
|
7700
|
-
this.symbolInput.focus();
|
|
7701
|
-
} */
|
|
7702
|
-
|
|
7703
8385
|
showError(message) {
|
|
7704
8386
|
this.hideLoading();
|
|
7705
8387
|
this.clearError();
|
|
@@ -7772,19 +8454,135 @@ ${SharedStyles}
|
|
|
7772
8454
|
this.container.appendChild(noDataElement);
|
|
7773
8455
|
}
|
|
7774
8456
|
}
|
|
8457
|
+
|
|
8458
|
+
/**
|
|
8459
|
+
* Show Options widget in a modal for a specific option contract
|
|
8460
|
+
* @param {string} optionSymbol - The option contract symbol
|
|
8461
|
+
*/
|
|
8462
|
+
showOptionsModal(optionSymbol) {
|
|
8463
|
+
if (!optionSymbol || optionSymbol === '--') {
|
|
8464
|
+
return;
|
|
8465
|
+
}
|
|
8466
|
+
|
|
8467
|
+
// Close existing modal if any
|
|
8468
|
+
this.closeOptionsModal();
|
|
8469
|
+
|
|
8470
|
+
// Create modal overlay
|
|
8471
|
+
this.optionsModal = document.createElement('div');
|
|
8472
|
+
this.optionsModal.className = 'option-chain-modal-overlay';
|
|
8473
|
+
this.optionsModal.innerHTML = `
|
|
8474
|
+
<div class="option-chain-modal">
|
|
8475
|
+
<div class="option-chain-modal-header">
|
|
8476
|
+
<h3>Option Details: ${sanitizeSymbol(optionSymbol)}</h3>
|
|
8477
|
+
<button class="option-chain-modal-close" aria-label="Close modal">×</button>
|
|
8478
|
+
</div>
|
|
8479
|
+
<div class="option-chain-modal-body">
|
|
8480
|
+
<div id="option-chain-modal-widget"></div>
|
|
8481
|
+
</div>
|
|
8482
|
+
</div>
|
|
8483
|
+
`;
|
|
8484
|
+
|
|
8485
|
+
// Add to body
|
|
8486
|
+
document.body.appendChild(this.optionsModal);
|
|
8487
|
+
|
|
8488
|
+
// Add close event
|
|
8489
|
+
const closeBtn = this.optionsModal.querySelector('.option-chain-modal-close');
|
|
8490
|
+
this.addEventListener(closeBtn, 'click', () => this.closeOptionsModal());
|
|
8491
|
+
|
|
8492
|
+
// Close on overlay click
|
|
8493
|
+
this.addEventListener(this.optionsModal, 'click', e => {
|
|
8494
|
+
if (e.target === this.optionsModal) {
|
|
8495
|
+
this.closeOptionsModal();
|
|
8496
|
+
}
|
|
8497
|
+
});
|
|
8498
|
+
|
|
8499
|
+
// Close on Escape key
|
|
8500
|
+
const escapeHandler = e => {
|
|
8501
|
+
if (e.key === 'Escape') {
|
|
8502
|
+
this.closeOptionsModal();
|
|
8503
|
+
}
|
|
8504
|
+
};
|
|
8505
|
+
document.addEventListener('keydown', escapeHandler);
|
|
8506
|
+
this._escapeHandler = escapeHandler;
|
|
8507
|
+
|
|
8508
|
+
// Create Options widget instance
|
|
8509
|
+
try {
|
|
8510
|
+
const widgetContainer = this.optionsModal.querySelector('#option-chain-modal-widget');
|
|
8511
|
+
this.optionsWidgetInstance = new OptionsWidget(widgetContainer, {
|
|
8512
|
+
symbol: optionSymbol,
|
|
8513
|
+
wsManager: this.wsManager,
|
|
8514
|
+
debug: this.debug,
|
|
8515
|
+
styled: true
|
|
8516
|
+
}, `${this.widgetId}-options-modal`);
|
|
8517
|
+
if (this.debug) {
|
|
8518
|
+
console.log('[OptionChainWidget] Options modal opened for:', optionSymbol);
|
|
8519
|
+
}
|
|
8520
|
+
} catch (error) {
|
|
8521
|
+
console.error('[OptionChainWidget] Error creating Options widget:', error);
|
|
8522
|
+
this.closeOptionsModal();
|
|
8523
|
+
this.showError(`Failed to load option details: ${error.message}`);
|
|
8524
|
+
}
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
/**
|
|
8528
|
+
* Close the Options modal and cleanup
|
|
8529
|
+
*/
|
|
8530
|
+
closeOptionsModal() {
|
|
8531
|
+
// Destroy widget instance
|
|
8532
|
+
if (this.optionsWidgetInstance) {
|
|
8533
|
+
this.optionsWidgetInstance.destroy();
|
|
8534
|
+
this.optionsWidgetInstance = null;
|
|
8535
|
+
}
|
|
8536
|
+
|
|
8537
|
+
// Remove modal from DOM
|
|
8538
|
+
if (this.optionsModal) {
|
|
8539
|
+
this.optionsModal.remove();
|
|
8540
|
+
this.optionsModal = null;
|
|
8541
|
+
}
|
|
8542
|
+
|
|
8543
|
+
// Remove escape key handler
|
|
8544
|
+
if (this._escapeHandler) {
|
|
8545
|
+
document.removeEventListener('keydown', this._escapeHandler);
|
|
8546
|
+
this._escapeHandler = null;
|
|
8547
|
+
}
|
|
8548
|
+
if (this.debug) {
|
|
8549
|
+
console.log('[OptionChainWidget] Options modal closed');
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
7775
8552
|
destroy() {
|
|
7776
8553
|
this.isDestroyed = true;
|
|
8554
|
+
|
|
8555
|
+
// Close modal if open
|
|
8556
|
+
this.closeOptionsModal();
|
|
7777
8557
|
if (this.unsubscribe) {
|
|
7778
8558
|
this.unsubscribe();
|
|
7779
8559
|
this.unsubscribe = null;
|
|
7780
8560
|
}
|
|
7781
8561
|
|
|
8562
|
+
// Unsubscribe from underlying price
|
|
8563
|
+
if (this.unsubscribeUnderlying) {
|
|
8564
|
+
this.unsubscribeUnderlying();
|
|
8565
|
+
this.unsubscribeUnderlying = null;
|
|
8566
|
+
}
|
|
8567
|
+
|
|
7782
8568
|
// MEMORY LEAK FIX: Clear loading timeout using BaseWidget method
|
|
7783
8569
|
if (this.loadingTimeout) {
|
|
7784
8570
|
this.clearTimeout(this.loadingTimeout);
|
|
7785
8571
|
this.loadingTimeout = null;
|
|
7786
8572
|
}
|
|
7787
8573
|
|
|
8574
|
+
// Clear debounce timeout
|
|
8575
|
+
if (this.renderDebounceTimeout) {
|
|
8576
|
+
this.clearTimeout(this.renderDebounceTimeout);
|
|
8577
|
+
this.renderDebounceTimeout = null;
|
|
8578
|
+
}
|
|
8579
|
+
|
|
8580
|
+
// Clear cached data
|
|
8581
|
+
this.cachedOptionChainData = null;
|
|
8582
|
+
this.cachedUnderlyingPrice = null;
|
|
8583
|
+
this.underlyingPrice = null;
|
|
8584
|
+
this.data = null;
|
|
8585
|
+
|
|
7788
8586
|
// Call parent destroy for automatic cleanup of event listeners, timeouts, intervals
|
|
7789
8587
|
super.destroy();
|
|
7790
8588
|
}
|
|
@@ -7870,80 +8668,81 @@ ${SharedStyles}
|
|
|
7870
8668
|
font-family: Arial, sans-serif;
|
|
7871
8669
|
}
|
|
7872
8670
|
|
|
7873
|
-
.widget-header {
|
|
8671
|
+
.data-widget .widget-header {
|
|
7874
8672
|
display: flex;
|
|
7875
8673
|
flex-direction: column;
|
|
7876
8674
|
margin-bottom: 10px;
|
|
7877
8675
|
}
|
|
7878
8676
|
|
|
7879
|
-
.symbol {
|
|
8677
|
+
.data-widget .symbol {
|
|
7880
8678
|
font-size: 24px;
|
|
7881
8679
|
font-weight: bold;
|
|
7882
8680
|
margin-right: 10px;
|
|
7883
8681
|
}
|
|
7884
8682
|
|
|
7885
|
-
.company-info {
|
|
8683
|
+
.data-widget .company-info {
|
|
7886
8684
|
font-size: 12px;
|
|
7887
8685
|
color: #666;
|
|
7888
8686
|
}
|
|
7889
8687
|
|
|
7890
|
-
.trading-info {
|
|
8688
|
+
.data-widget .trading-info {
|
|
7891
8689
|
font-size: 12px;
|
|
7892
8690
|
color: #999;
|
|
7893
8691
|
}
|
|
7894
8692
|
|
|
7895
|
-
.price-section {
|
|
8693
|
+
.data-widget .price-section {
|
|
7896
8694
|
display: flex;
|
|
7897
8695
|
align-items: baseline;
|
|
7898
8696
|
margin-bottom: 10px;
|
|
7899
8697
|
}
|
|
7900
8698
|
|
|
7901
|
-
.current-price {
|
|
8699
|
+
.data-widget .current-price {
|
|
7902
8700
|
font-size: 36px;
|
|
7903
8701
|
font-weight: bold;
|
|
7904
8702
|
margin-right: 10px;
|
|
7905
8703
|
}
|
|
7906
8704
|
|
|
7907
|
-
.price-change {
|
|
8705
|
+
.data-widget .price-change {
|
|
7908
8706
|
font-size: 18px;
|
|
7909
8707
|
}
|
|
7910
8708
|
|
|
7911
|
-
.price-change.positive {
|
|
8709
|
+
.data-widget .price-change.positive {
|
|
7912
8710
|
color: green;
|
|
7913
8711
|
}
|
|
7914
8712
|
|
|
7915
|
-
.price-change.negative {
|
|
8713
|
+
.data-widget .price-change.negative {
|
|
7916
8714
|
color: red;
|
|
7917
8715
|
}
|
|
7918
8716
|
|
|
7919
|
-
.bid-ask-section {
|
|
8717
|
+
.data-widget .bid-ask-section {
|
|
7920
8718
|
display: flex;
|
|
7921
8719
|
justify-content: space-between;
|
|
7922
8720
|
margin-bottom: 10px;
|
|
7923
8721
|
}
|
|
7924
8722
|
|
|
7925
|
-
.ask,
|
|
8723
|
+
.data-widget .ask,
|
|
8724
|
+
.data-widget .bid {
|
|
7926
8725
|
display: flex;
|
|
7927
8726
|
align-items: center;
|
|
7928
8727
|
}
|
|
7929
8728
|
|
|
7930
|
-
.label {
|
|
8729
|
+
.data-widget .label {
|
|
7931
8730
|
font-size: 14px;
|
|
7932
8731
|
margin-right: 5px;
|
|
7933
8732
|
}
|
|
7934
8733
|
|
|
7935
|
-
.value {
|
|
8734
|
+
.data-widget .value {
|
|
7936
8735
|
font-size: 16px;
|
|
7937
8736
|
font-weight: bold;
|
|
7938
8737
|
margin-right: 5px;
|
|
7939
8738
|
}
|
|
7940
8739
|
|
|
7941
|
-
.size {
|
|
8740
|
+
.data-widget .size {
|
|
7942
8741
|
font-size: 14px;
|
|
7943
8742
|
color: red;
|
|
7944
8743
|
}
|
|
7945
8744
|
|
|
7946
|
-
.widget-footer {
|
|
8745
|
+
.data-widget .widget-footer {
|
|
7947
8746
|
font-size: 12px;
|
|
7948
8747
|
color: #333;
|
|
7949
8748
|
}
|
|
@@ -8354,76 +9153,50 @@ ${SharedStyles}
|
|
|
8354
9153
|
|
|
8355
9154
|
// Subscribe to regular market data (Level 1)
|
|
8356
9155
|
// Create a wrapper to add data type context
|
|
8357
|
-
this.unsubscribeMarket = this.wsManager.subscribe(`${this.widgetId}-market`, ['queryl1'],
|
|
8358
|
-
const {
|
|
8359
|
-
event,
|
|
8360
|
-
data
|
|
8361
|
-
} = messageWrapper;
|
|
8362
|
-
|
|
8363
|
-
// Handle connection events
|
|
8364
|
-
if (event === 'connection') {
|
|
8365
|
-
this.handleConnectionStatus(data);
|
|
8366
|
-
return;
|
|
8367
|
-
}
|
|
8368
|
-
|
|
8369
|
-
// For data events, add type context and use base handleMessage pattern
|
|
8370
|
-
if (event === 'data') {
|
|
8371
|
-
data._dataType = 'market';
|
|
8372
|
-
this.handleMessage({
|
|
8373
|
-
event,
|
|
8374
|
-
data
|
|
8375
|
-
});
|
|
8376
|
-
}
|
|
8377
|
-
}, this.symbol);
|
|
9156
|
+
this.unsubscribeMarket = this.wsManager.subscribe(`${this.widgetId}-market`, ['queryl1'], this.handleMessage.bind(this), this.symbol);
|
|
8378
9157
|
|
|
8379
9158
|
// Subscribe to night session data (BlueOcean or Bruce)
|
|
8380
|
-
this.unsubscribeNight = this.wsManager.subscribe(`${this.widgetId}-night`, ['queryblueoceanl1'],
|
|
8381
|
-
const {
|
|
8382
|
-
event,
|
|
8383
|
-
data
|
|
8384
|
-
} = messageWrapper;
|
|
8385
|
-
|
|
8386
|
-
// Connection already handled by market subscription
|
|
8387
|
-
if (event === 'connection') {
|
|
8388
|
-
return;
|
|
8389
|
-
}
|
|
8390
|
-
|
|
8391
|
-
// For data events, add type context and use base handleMessage pattern
|
|
8392
|
-
if (event === 'data') {
|
|
8393
|
-
data._dataType = 'night';
|
|
8394
|
-
this.handleMessage({
|
|
8395
|
-
event,
|
|
8396
|
-
data
|
|
8397
|
-
});
|
|
8398
|
-
}
|
|
8399
|
-
}, this.symbol);
|
|
9159
|
+
this.unsubscribeNight = this.wsManager.subscribe(`${this.widgetId}-night`, ['queryblueoceanl1'], this.handleMessage.bind(this), this.symbol);
|
|
8400
9160
|
}
|
|
8401
9161
|
handleData(message) {
|
|
8402
9162
|
// Extract data type from metadata
|
|
8403
|
-
const dataType = message.
|
|
9163
|
+
const dataType = message.type;
|
|
8404
9164
|
if (this.debug) {
|
|
8405
9165
|
console.log(`[CombinedMarketWidget] handleData called with type: ${dataType}`, message);
|
|
8406
9166
|
}
|
|
8407
9167
|
|
|
8408
|
-
//
|
|
8409
|
-
if (
|
|
8410
|
-
|
|
8411
|
-
|
|
9168
|
+
// Clear loading timeout since we received data (use BaseWidget method)
|
|
9169
|
+
if (this.loadingTimeout) {
|
|
9170
|
+
this.clearTimeout(this.loadingTimeout);
|
|
9171
|
+
this.loadingTimeout = null;
|
|
9172
|
+
}
|
|
9173
|
+
|
|
9174
|
+
// Handle option chain data
|
|
9175
|
+
if (Array.isArray(message)) {
|
|
9176
|
+
if (message.length === 0) {
|
|
9177
|
+
// Only show no data state if we don't have cached data
|
|
9178
|
+
if (!this.data) {
|
|
9179
|
+
this.showNoDataState();
|
|
9180
|
+
} else {
|
|
9181
|
+
this.hideLoading();
|
|
9182
|
+
if (this.debug) {
|
|
9183
|
+
console.log('[OptionChainWidget] No new data, keeping cached data visible');
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
8412
9186
|
}
|
|
8413
|
-
return;
|
|
8414
9187
|
}
|
|
8415
9188
|
|
|
8416
9189
|
// Handle error messages from server
|
|
8417
|
-
if (message.type === 'error'
|
|
9190
|
+
if (message.type === 'error' || message.error == true) {
|
|
8418
9191
|
if (this.debug) {
|
|
8419
9192
|
console.log(`[CombinedMarketWidget] Received no data message for ${dataType}:`, message.message);
|
|
8420
9193
|
}
|
|
8421
9194
|
// Keep existing cached data visible, just hide loading
|
|
8422
|
-
if (dataType === '
|
|
9195
|
+
if (dataType === 'queryl1' && !this.marketData) {
|
|
8423
9196
|
if (this.debug) {
|
|
8424
9197
|
console.log('[CombinedMarketWidget] No market data available');
|
|
8425
9198
|
}
|
|
8426
|
-
} else if (dataType === '
|
|
9199
|
+
} else if (dataType === 'blueoceanl1' && !this.nightSessionData) {
|
|
8427
9200
|
if (this.debug) {
|
|
8428
9201
|
console.log('[CombinedMarketWidget] No night session data available');
|
|
8429
9202
|
}
|
|
@@ -8876,7 +9649,7 @@ ${SharedStyles}
|
|
|
8876
9649
|
margin: 0 auto;
|
|
8877
9650
|
}
|
|
8878
9651
|
|
|
8879
|
-
.chart-header {
|
|
9652
|
+
.intraday-chart-widget .chart-header {
|
|
8880
9653
|
display: flex;
|
|
8881
9654
|
justify-content: space-between;
|
|
8882
9655
|
align-items: center;
|
|
@@ -8885,13 +9658,13 @@ ${SharedStyles}
|
|
|
8885
9658
|
border-bottom: 1px solid #e5e7eb;
|
|
8886
9659
|
}
|
|
8887
9660
|
|
|
8888
|
-
.chart-title-section {
|
|
9661
|
+
.intraday-chart-widget .chart-title-section {
|
|
8889
9662
|
display: flex;
|
|
8890
9663
|
flex-direction: column;
|
|
8891
9664
|
gap: 4px;
|
|
8892
9665
|
}
|
|
8893
9666
|
|
|
8894
|
-
.company-market-info {
|
|
9667
|
+
.intraday-chart-widget .company-market-info {
|
|
8895
9668
|
display: flex;
|
|
8896
9669
|
gap: 8px;
|
|
8897
9670
|
align-items: center;
|
|
@@ -8899,18 +9672,18 @@ ${SharedStyles}
|
|
|
8899
9672
|
color: #6b7280;
|
|
8900
9673
|
}
|
|
8901
9674
|
|
|
8902
|
-
.intraday-company-name {
|
|
9675
|
+
.intraday-chart-widget .intraday-company-name {
|
|
8903
9676
|
font-weight: 500;
|
|
8904
9677
|
}
|
|
8905
9678
|
|
|
8906
|
-
.intraday-chart-symbol {
|
|
9679
|
+
.intraday-chart-widget .intraday-chart-symbol {
|
|
8907
9680
|
font-size: 1.5em;
|
|
8908
9681
|
font-weight: 700;
|
|
8909
9682
|
color: #1f2937;
|
|
8910
9683
|
margin: 0;
|
|
8911
9684
|
}
|
|
8912
9685
|
|
|
8913
|
-
.intraday-chart-source {
|
|
9686
|
+
.intraday-chart-widget .intraday-chart-source {
|
|
8914
9687
|
font-size: 0.75em;
|
|
8915
9688
|
padding: 4px 8px;
|
|
8916
9689
|
border-radius: 4px;
|
|
@@ -8919,36 +9692,36 @@ ${SharedStyles}
|
|
|
8919
9692
|
font-weight: 600;
|
|
8920
9693
|
}
|
|
8921
9694
|
|
|
8922
|
-
.chart-change {
|
|
9695
|
+
.intraday-chart-widget .chart-change {
|
|
8923
9696
|
font-size: 1.1em;
|
|
8924
9697
|
font-weight: 600;
|
|
8925
9698
|
padding: 6px 12px;
|
|
8926
9699
|
border-radius: 6px;
|
|
8927
9700
|
}
|
|
8928
9701
|
|
|
8929
|
-
.chart-change.positive {
|
|
9702
|
+
.intraday-chart-widget .chart-change.positive {
|
|
8930
9703
|
color: #059669;
|
|
8931
9704
|
background: #d1fae5;
|
|
8932
9705
|
}
|
|
8933
9706
|
|
|
8934
|
-
.chart-change.negative {
|
|
9707
|
+
.intraday-chart-widget .chart-change.negative {
|
|
8935
9708
|
color: #dc2626;
|
|
8936
9709
|
background: #fee2e2;
|
|
8937
9710
|
}
|
|
8938
9711
|
|
|
8939
|
-
.chart-controls {
|
|
9712
|
+
.intraday-chart-widget .chart-controls {
|
|
8940
9713
|
display: flex;
|
|
8941
9714
|
justify-content: space-between;
|
|
8942
9715
|
align-items: center;
|
|
8943
9716
|
margin-bottom: 15px;
|
|
8944
9717
|
}
|
|
8945
9718
|
|
|
8946
|
-
.chart-range-selector {
|
|
9719
|
+
.intraday-chart-widget .chart-range-selector {
|
|
8947
9720
|
display: flex;
|
|
8948
9721
|
gap: 8px;
|
|
8949
9722
|
}
|
|
8950
9723
|
|
|
8951
|
-
.range-btn {
|
|
9724
|
+
.intraday-chart-widget .range-btn {
|
|
8952
9725
|
padding: 8px 16px;
|
|
8953
9726
|
border: 1px solid #e5e7eb;
|
|
8954
9727
|
background: white;
|
|
@@ -8960,28 +9733,28 @@ ${SharedStyles}
|
|
|
8960
9733
|
transition: all 0.2s ease;
|
|
8961
9734
|
}
|
|
8962
9735
|
|
|
8963
|
-
.range-btn:hover {
|
|
9736
|
+
.intraday-chart-widget .range-btn:hover {
|
|
8964
9737
|
background: #f9fafb;
|
|
8965
9738
|
border-color: #d1d5db;
|
|
8966
9739
|
}
|
|
8967
9740
|
|
|
8968
|
-
.range-btn.active {
|
|
9741
|
+
.intraday-chart-widget .range-btn.active {
|
|
8969
9742
|
background: #667eea;
|
|
8970
9743
|
color: white;
|
|
8971
9744
|
border-color: #667eea;
|
|
8972
9745
|
}
|
|
8973
9746
|
|
|
8974
|
-
.range-btn:focus {
|
|
9747
|
+
.intraday-chart-widget .range-btn:focus {
|
|
8975
9748
|
outline: none;
|
|
8976
9749
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
8977
9750
|
}
|
|
8978
9751
|
|
|
8979
|
-
.chart-type-selector {
|
|
9752
|
+
.intraday-chart-widget .chart-type-selector {
|
|
8980
9753
|
display: flex;
|
|
8981
9754
|
gap: 8px;
|
|
8982
9755
|
}
|
|
8983
9756
|
|
|
8984
|
-
.type-btn {
|
|
9757
|
+
.intraday-chart-widget .type-btn {
|
|
8985
9758
|
padding: 8px 16px;
|
|
8986
9759
|
border: 1px solid #e5e7eb;
|
|
8987
9760
|
background: white;
|
|
@@ -8993,23 +9766,23 @@ ${SharedStyles}
|
|
|
8993
9766
|
transition: all 0.2s ease;
|
|
8994
9767
|
}
|
|
8995
9768
|
|
|
8996
|
-
.type-btn:hover {
|
|
9769
|
+
.intraday-chart-widget .type-btn:hover {
|
|
8997
9770
|
background: #f9fafb;
|
|
8998
9771
|
border-color: #d1d5db;
|
|
8999
9772
|
}
|
|
9000
9773
|
|
|
9001
|
-
.type-btn.active {
|
|
9774
|
+
.intraday-chart-widget .type-btn.active {
|
|
9002
9775
|
background: #10b981;
|
|
9003
9776
|
color: white;
|
|
9004
9777
|
border-color: #10b981;
|
|
9005
9778
|
}
|
|
9006
9779
|
|
|
9007
|
-
.type-btn:focus {
|
|
9780
|
+
.intraday-chart-widget .type-btn:focus {
|
|
9008
9781
|
outline: none;
|
|
9009
9782
|
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
|
9010
9783
|
}
|
|
9011
9784
|
|
|
9012
|
-
.zoom-reset-btn {
|
|
9785
|
+
.intraday-chart-widget .zoom-reset-btn {
|
|
9013
9786
|
display: flex;
|
|
9014
9787
|
align-items: center;
|
|
9015
9788
|
gap: 6px;
|
|
@@ -9024,34 +9797,34 @@ ${SharedStyles}
|
|
|
9024
9797
|
transition: all 0.2s ease;
|
|
9025
9798
|
}
|
|
9026
9799
|
|
|
9027
|
-
.zoom-reset-btn:hover {
|
|
9800
|
+
.intraday-chart-widget .zoom-reset-btn:hover {
|
|
9028
9801
|
background: #f9fafb;
|
|
9029
9802
|
border-color: #667eea;
|
|
9030
9803
|
color: #667eea;
|
|
9031
9804
|
}
|
|
9032
9805
|
|
|
9033
|
-
.zoom-reset-btn:focus {
|
|
9806
|
+
.intraday-chart-widget .zoom-reset-btn:focus {
|
|
9034
9807
|
outline: none;
|
|
9035
9808
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
9036
9809
|
}
|
|
9037
9810
|
|
|
9038
|
-
.zoom-reset-btn svg {
|
|
9811
|
+
.intraday-chart-widget .zoom-reset-btn svg {
|
|
9039
9812
|
flex-shrink: 0;
|
|
9040
9813
|
}
|
|
9041
9814
|
|
|
9042
|
-
.chart-container {
|
|
9815
|
+
.intraday-chart-widget .chart-container {
|
|
9043
9816
|
height: 500px;
|
|
9044
9817
|
margin-bottom: 20px;
|
|
9045
9818
|
position: relative;
|
|
9046
9819
|
}
|
|
9047
9820
|
|
|
9048
|
-
.chart-stats {
|
|
9821
|
+
.intraday-chart-widget .chart-stats {
|
|
9049
9822
|
padding: 15px;
|
|
9050
9823
|
background: #f9fafb;
|
|
9051
9824
|
border-radius: 8px;
|
|
9052
9825
|
}
|
|
9053
9826
|
|
|
9054
|
-
.stats-header {
|
|
9827
|
+
.intraday-chart-widget .stats-header {
|
|
9055
9828
|
font-size: 0.875em;
|
|
9056
9829
|
font-weight: 700;
|
|
9057
9830
|
color: #374151;
|
|
@@ -9062,19 +9835,19 @@ ${SharedStyles}
|
|
|
9062
9835
|
border-bottom: 2px solid #e5e7eb;
|
|
9063
9836
|
}
|
|
9064
9837
|
|
|
9065
|
-
.stats-grid {
|
|
9838
|
+
.intraday-chart-widget .stats-grid {
|
|
9066
9839
|
display: grid;
|
|
9067
9840
|
grid-template-columns: repeat(5, 1fr);
|
|
9068
9841
|
gap: 15px;
|
|
9069
9842
|
}
|
|
9070
9843
|
|
|
9071
|
-
.stat-item {
|
|
9844
|
+
.intraday-chart-widget .stat-item {
|
|
9072
9845
|
display: flex;
|
|
9073
9846
|
flex-direction: column;
|
|
9074
9847
|
gap: 4px;
|
|
9075
9848
|
}
|
|
9076
9849
|
|
|
9077
|
-
.stat-label {
|
|
9850
|
+
.intraday-chart-widget .stat-label {
|
|
9078
9851
|
font-size: 0.75em;
|
|
9079
9852
|
color: #6b7280;
|
|
9080
9853
|
font-weight: 600;
|
|
@@ -9082,13 +9855,13 @@ ${SharedStyles}
|
|
|
9082
9855
|
letter-spacing: 0.5px;
|
|
9083
9856
|
}
|
|
9084
9857
|
|
|
9085
|
-
.stat-value {
|
|
9858
|
+
.intraday-chart-widget .stat-value {
|
|
9086
9859
|
font-size: 1.1em;
|
|
9087
9860
|
font-weight: 700;
|
|
9088
9861
|
color: #1f2937;
|
|
9089
9862
|
}
|
|
9090
9863
|
|
|
9091
|
-
.widget-loading-overlay {
|
|
9864
|
+
.intraday-chart-widget .widget-loading-overlay {
|
|
9092
9865
|
position: absolute;
|
|
9093
9866
|
top: 0;
|
|
9094
9867
|
left: 0;
|
|
@@ -9107,26 +9880,26 @@ ${SharedStyles}
|
|
|
9107
9880
|
backdrop-filter: blur(1px);
|
|
9108
9881
|
}
|
|
9109
9882
|
|
|
9110
|
-
.widget-loading-overlay.hidden {
|
|
9883
|
+
.intraday-chart-widget .widget-loading-overlay.hidden {
|
|
9111
9884
|
display: none;
|
|
9112
9885
|
}
|
|
9113
9886
|
|
|
9114
|
-
.loading-spinner {
|
|
9887
|
+
.intraday-chart-widget .loading-spinner {
|
|
9115
9888
|
width: 20px;
|
|
9116
9889
|
height: 20px;
|
|
9117
9890
|
border: 3px solid #e5e7eb;
|
|
9118
9891
|
border-top-color: #667eea;
|
|
9119
9892
|
border-radius: 50%;
|
|
9120
|
-
animation: spin 0.8s linear infinite;
|
|
9893
|
+
animation: intraday-spin 0.8s linear infinite;
|
|
9121
9894
|
}
|
|
9122
9895
|
|
|
9123
|
-
.loading-text {
|
|
9896
|
+
.intraday-chart-widget .loading-text {
|
|
9124
9897
|
color: #6b7280;
|
|
9125
9898
|
font-size: 0.875em;
|
|
9126
9899
|
font-weight: 500;
|
|
9127
9900
|
}
|
|
9128
9901
|
|
|
9129
|
-
@keyframes spin {
|
|
9902
|
+
@keyframes intraday-spin {
|
|
9130
9903
|
to { transform: rotate(360deg); }
|
|
9131
9904
|
}
|
|
9132
9905
|
|
|
@@ -9136,26 +9909,26 @@ ${SharedStyles}
|
|
|
9136
9909
|
padding: 15px;
|
|
9137
9910
|
}
|
|
9138
9911
|
|
|
9139
|
-
.stats-grid {
|
|
9912
|
+
.intraday-chart-widget .stats-grid {
|
|
9140
9913
|
grid-template-columns: repeat(3, 1fr);
|
|
9141
9914
|
gap: 10px;
|
|
9142
9915
|
}
|
|
9143
9916
|
|
|
9144
|
-
.stats-header {
|
|
9917
|
+
.intraday-chart-widget .stats-header {
|
|
9145
9918
|
font-size: 0.8em;
|
|
9146
9919
|
margin-bottom: 10px;
|
|
9147
9920
|
}
|
|
9148
9921
|
|
|
9149
|
-
.chart-container {
|
|
9922
|
+
.intraday-chart-widget .chart-container {
|
|
9150
9923
|
height: 350px;
|
|
9151
9924
|
}
|
|
9152
9925
|
|
|
9153
|
-
.intraday-chart-symbol {
|
|
9926
|
+
.intraday-chart-widget .intraday-chart-symbol {
|
|
9154
9927
|
font-size: 1.2em;
|
|
9155
9928
|
}
|
|
9156
9929
|
}
|
|
9157
9930
|
|
|
9158
|
-
.widget-error {
|
|
9931
|
+
.intraday-chart-widget .widget-error {
|
|
9159
9932
|
padding: 15px;
|
|
9160
9933
|
background: #fee2e2;
|
|
9161
9934
|
border: 1px solid #fecaca;
|
|
@@ -42025,8 +42798,8 @@ ${SharedStyles}
|
|
|
42025
42798
|
// Pass messageType from parent message, don't extract from each data item
|
|
42026
42799
|
this._processDataItem(dataItem, relevantWidgets, messageType);
|
|
42027
42800
|
});
|
|
42028
|
-
} else if (item && item[0] && item[0].Strike !== undefined && item[0].Expire && !item[0].underlyingSymbol) {
|
|
42029
|
-
this._processOptionChainData(item, relevantWidgets);
|
|
42801
|
+
} else if (item.data && item.data[0] && item.data[0].Strike !== undefined && item.data[0].Expire && !item.data[0].underlyingSymbol) {
|
|
42802
|
+
this._processOptionChainData(item.data, relevantWidgets);
|
|
42030
42803
|
} else {
|
|
42031
42804
|
// Process single item - messageType already extracted from message
|
|
42032
42805
|
this._processDataItem(item, relevantWidgets, messageType);
|
|
@@ -42035,6 +42808,7 @@ ${SharedStyles}
|
|
|
42035
42808
|
|
|
42036
42809
|
// Simplified option chain processing - handle entire array at once
|
|
42037
42810
|
_processOptionChainData(optionChainArray, relevantWidgets) {
|
|
42811
|
+
//console.log('PROCESSING DATA OPTIONS')
|
|
42038
42812
|
if (!optionChainArray || optionChainArray.length === 0) return;
|
|
42039
42813
|
// Extract underlying symbol and date from first option contract
|
|
42040
42814
|
const firstOption = optionChainArray[0];
|