@lovebowls/leagueelements
Version:
League Elements package for LoveBowls
1,504 lines (1,360 loc) • 867 kB
JavaScript
var LeagueElement = (function (exports) {
'use strict';
const baseFontSizeConstants = (fontScale = 1.0) => `
/* Base font sizes for different contexts */
--le-font-size-base-desktop: ${16 * fontScale}px;
--le-font-size-base-mobile: ${18 * fontScale}px;
line-height: 1.2;
`;
// Shared font size base variables and UI element font sizes (used internally)
const getFontSizeElementVariables = (isMobile = false) => `
/* Specific sizes for common UI elements */
--le-font-size-button: var(--le-font-size-base-${isMobile ? 'mobile' : 'desktop'});
--le-font-size-label: var(--le-font-size-base-${isMobile ? 'mobile' : 'desktop'});
--le-font-size-input: var(--le-font-size-base-${isMobile ? 'mobile' : 'desktop'});
--le-font-size-table-header: var(--le-font-size-base-${isMobile ? 'mobile' : 'desktop'});
--le-font-size-table-cell: 0.7em;
--le-font-size-dropdown: var(--le-font-size-base-${isMobile ? 'mobile' : 'desktop'});
--le-padding-xs: 0.25rem;
--le-padding-s: 0.6rem;
--le-padding-m: 1rem;
`;
const getMobileStyles = (fontScale = 1.0) => `
:host {
${baseFontSizeConstants(fontScale)}
font-size: var(--le-font-size-base-mobile);
/* Mobile-specific styling that can be added to host elements */
--le-font-size-base: 1em;
--le-font-size-xs: 0.6em;
--le-font-size-small: 0.8em;
--le-font-size-medium: 1.0em;
--le-font-size-large: 1.2em;
--le-font-size-xlarge: 1.4em;
--le-font-size-xxlarge: 1.6em;
${getFontSizeElementVariables(true)}
.no-data
padding: var(--le-padding-m, 1rem);
color: var(--le-text-color-secondary, #666);
background-color: transparent;
border: none;
}
.controls-panel {
gap: var(--le-padding-s, 0.5rem);
}
/* Mobile-specific dropdown styling */
.controls-panel .filter-controls .dropdown-shared {
flex: 1;
}
.controls-panel .dropdown-shared {
flex: 1;
}
.controls-panel .dropdown-shared .dropdown-select-shared {
width: 100%;
padding: var(--le-padding-s, 0.75rem) calc(var(--le-padding-m, 1rem) * 2.5) var(--le-padding-s, 0.75rem) var(--le-padding-m, 1rem);
min-height: 44px; /* Minimum touch target size */
background-size: 1.2rem;
background-position: right 1rem center;
border-width: 2px;
}
.controls-panel .dropdown-select-shared:focus {
border-width: 2px;
}
/* Input fields on mobile */
.form-input-shared,
.form-select-shared,
{
min-height: 44px; /* Standard mobile touch target */
padding: var(--le-padding-xs, 0.5rem) var(--le-padding-m, 1rem);
font-size: var(--le-font-size-medium, 1.0em);
border-width: 2px;
width: 100% !important; /* Force width consistency on mobile */
max-width: 100%;
box-sizing: border-box;
}
/* Specific handling for date inputs on mobile */
.form-input-shared[type="date"] {
-webkit-appearance: none;
-moz-appearance: textfield;
appearance: none;
}
.form-input-shared:focus, .form-select-shared:focus {
border-width: 2px;
}
.form-label-shared {
margin-bottom: var(--le-padding-s, 0.75rem);
}
.resizer {
display: none !important;
}
}
`;
const getDesktopStyles = (fontScale = 1.0) => `
:host {
${baseFontSizeConstants(fontScale)}
font-size: var(--le-font-size-base-desktop);
/* Standardized font sizes for desktop - these cascade to all sub-components */
--le-font-size-base: 1em;
--le-font-size-xs: 0.75em;
--le-font-size-small: 0.9em;
--le-font-size-medium: 1.0em;
--le-font-size-large: 1.2em;
--le-font-size-xlarge: 1.4em;
--le-font-size-xxlarge: 1.6em;
${getFontSizeElementVariables(false)}
.no-data
padding: var(--le-padding-m, 1rem);
color: var(--le-text-color-secondary, #666);
background-color: transparent;
border: none;
}
/* Desktop-specific dropdown styling */
.controls-panel .filter-controls .dropdown-shared {
width: auto;
min-width: 200px;
}
.controls-panel .dropdown-shared .dropdown-select-shared {
min-width: 160px;
width: auto;
}
.resizer {
width: 5px;
min-width: 5px;
cursor: col-resize;
background-color: var(--swal-background-color-header);
border-left: 1px solid var(--swal-border-color-light);
border-right: 1px solid var(--swal-border-color-light);
z-index: 10;
}
.resizer:hover {
background: var(--le-text-color-secondary);
}
}
`;
// Shared base styles for dropdown elements
const dropdownStyles = `
/* Shared base styles for dropdown select elements */
.dropdown-select-shared {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-color: var(--le-background-color-panel, #fff);
border: 1px solid var(--le-border-color-medium, #ddd);
border-radius: var(--le-border-radius-small, 3px);
padding: var(--le-padding-xs, 0.25rem);
font-size: var(--le-font-size-base, 1em);
color: var(--le-text-color-primary, #333);
cursor: pointer;
line-height: 1.4;
max-width: 100%;
width: auto;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.5em center;
background-size: 1em;
}
.dropdown-select-shared:hover {
border-color: var(--le-border-color-dark, #ccc);
}
.dropdown-select-shared:focus {
outline: none;
border-color: var(--le-text-color-accent, #2196f3);
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
}
.dropdown-shared {
position: relative;
display: inline-block;
}
/* Shared styles for dropdown menus (button-triggered) */
.list-item-actions {
position: relative; /* Ensure actions container can contain absolutely positioned menu */
}
.dropdown-menu {
position: fixed; /* Use fixed positioning to escape container boundaries */
background-color: var(--le-background-color-panel, #fff);
border: 1px solid var(--le-border-color-medium, #ddd);
border-radius: var(--le-border-radius-standard, 4px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
min-width: 120px;
z-index: 9999; /* Much higher z-index to appear above list containers */
margin-top: 2px; /* Small gap between button and menu */
}
.dropdown-menu-item {
padding: var(--le-padding-s, 0.5em) var(--le-padding-m, 0.75em);
cursor: pointer;
border-bottom: 1px solid var(--le-border-color-light, #eee);
transition: background-color 0.2s ease;
font-size: var(--le-font-size-base, 1em);
color: var(--le-text-color-primary, #333);
}
.dropdown-menu-item:last-child {
border-bottom: none;
}
.dropdown-menu-item:hover {
background-color: var(--le-background-color-hover, #f5f5f5);
}
.dropdown-menu-item:first-child {
border-top-left-radius: var(--le-border-radius-standard, 4px);
border-top-right-radius: var(--le-border-radius-standard, 4px);
}
.dropdown-menu-item:last-child {
border-bottom-left-radius: var(--le-border-radius-standard, 4px);
border-bottom-right-radius: var(--le-border-radius-standard, 4px);
}
`;
const panelStyles = `
.panel {
margin-bottom: var(--le-padding-m, 1.25em);
border: 1px solid var(--le-border-color-light, #f0f0f0);
border-radius: var(--le-border-radius-standard, 4px);
background-color: var(--le-background-color-panel, #fff);
}
.panel-header-shared {
padding: var(--le-padding-s, 0.75em) var(--le-padding-m, 1.25em); /* Increased padding */
border-bottom: 1px solid var(--le-border-color-medium, #eee);
font-weight: bold;
color: var(--le-text-color-primary, #333);
background-color: var(--le-background-color-header, #f9f9f9);
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--le-font-size-medium, 1.1em);
}
.panel .panel-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--le-background-color-header, #f9f9f9);
}
.panel-content {
padding: var(--le-padding-m, 1.25em);
background-color: var(--le-background-color-panel, #fff);
/* Common border for content area if needed
border: 1px solid var(--le-border-color-light, #f0f0f0);
*/
}
.controls-panel {
/* Layout properties moved to mobile/desktop sections */
padding: 0;
margin-bottom: var(--le-padding-s, 0.5rem);
margin-top: var(--le-padding-s, 0.5rem);
background: transparent;
border: none;
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--le-padding-m, 1rem);
}
.filter-controls {
gap: var(--le-padding-s, 0.5rem);
display: flex;
align-items: center;
flex: 1;
}
/* Enhanced dropdown styling for button-like appearance */
.controls-panel .dropdown-shared {
position: relative;
display: inline-block;
}
.controls-panel .dropdown-select-shared {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-color: var(--le-background-color-button, #f0f0f0);
border: 1px solid var(--le-border-color-medium, #ddd);
border-radius: var(--le-border-radius-standard, 4px);
padding: var(--le-padding-xs, 0.25rem);
font-size: var(--le-font-size-dropdown, 1em));
color: var(--le-text-color-primary, #333);
cursor: pointer;
line-height: 1.4;
min-width: 150px;
transition: all 0.2s ease;
font-weight: 500;
}
.controls-panel .dropdown-select-shared:hover {
background-color: var(--le-background-color-button-hover, #e0e0e0);
border-color: var(--le-border-color-dark, #ccc);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.controls-panel .dropdown-select-shared:focus {
outline: none;
border-color: var(--le-text-color-accent, #2196f3);
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
background-color: var(--le-background-color-panel, #fff);
}
.controls-panel .dropdown-select-shared:active {
background-color: var(--le-background-color-button-hover, #e0e0e0);
transform: translateY(1px);
}
`;
const buttonStyles = `
.button-shared {
padding: 4px 8px;
border: 1px solid var(--le-border-color-medium, #ccc);
background-color: var(--le-background-color-button, #f0f0f0);
color: var(--le-text-color-primary, #333); /* Ensure text color contrasts with button background */
cursor: pointer;
border-radius: var(--le-border-radius-standard, 4px);
font-size: var(--le-font-size-button, var(--le-font-size-medium, 1.15em)); /* Use variable with fallback */
text-decoration: none;
display: inline-block;
text-align: center;
line-height: normal; /* Ensure consistent line height */
white-space: nowrap; /* Prevent text wrapping */
vertical-align: middle; /* Align nicely if next to text/icons */
user-select: none; /* Prevent text selection on click */
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; /* Smooth transitions */
}
.button-shared:hover:not(:disabled) {
background-color: var(--le-background-color-button-hover, #e0e0e0);
border-color: var(--le-border-color-dark, #bbb); /* Slightly darker border on hover */
/* color: var(--le-text-color-accent-hover, inherit); Optional: change text color on hover */
}
.button-shared:active:not(:disabled) {
/* Optional: style for active (pressed) state */
/* background-color: var(--le-background-color-button-active, #d0d0d0); */
}
.button-shared:disabled,
.button-shared.disabled { /* Allow class-based disabling too */
background-color: var(--le-background-color-button-disabled, #eee);
color: var(--le-text-color-secondary, #aaa);
border-color: var(--le-border-color-medium, #ccc); /* Use medium border for disabled state */
cursor: not-allowed;
opacity: 0.7; /* Visually indicate disabled state */
}
/* Variations */
.button-shared.button-primary {
background-color: var(--le-color-primary, #007bff);
color: var(--le-text-color-on-primary, #fff);
border-color: var(--le-color-primary, #007bff);
}
.button-shared.button-primary:hover:not(:disabled) {
background-color: var(--le-color-primary-hover, #0056b3);
border-color: var(--le-color-primary-hover, #0056b3);
}
.button-shared.button-secondary-light {
background-color: var(--le-background-color-button-secondary-light, #f8f9fa);
color: var(--le-text-color-secondary-light-text, #212529);
border-color: var(--le-border-color-secondary-light, #ced4da);
}
.button-shared.button-secondary-light:hover:not(:disabled) {
background-color: var(--le-background-color-button-secondary-light-hover, #e2e6ea);
border-color: var(--le-border-color-secondary-light-hover, #dae0e5);
color: var(--le-text-color-secondary-light-text-hover, #212529);
}
/* Example for a darker secondary button if needed elsewhere
.button-shared.button-secondary {
background-color: var(--le-color-secondary, #6c757d);
color: var(--le-text-color-on-secondary, #fff);
border-color: var(--le-color-secondary, #6c757d);
}
.button-shared.button-secondary:hover:not(:disabled) {
background-color: var(--le-color-secondary-hover, #5a6268);
border-color: var(--le-color-secondary-hover, #5a6268);
}
*/
`;
const modalStyles = `
.modal-shared-overlay {
display: none; /* Hidden by default */
position: fixed;
z-index: var(--le-z-index-modal-overlay, 1000); /* Ensure it's on top */
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: var(--le-background-color-modal-overlay, rgba(0,0,0,0.4));
padding-top: 5%;
padding-bottom: 5%;
}
.modal-shared-overlay.open {
display: flex;
justify-content: center;
}
.modal-shared-content {
background-color: var(--le-background-color-panel, #fff);
margin: 10% auto; /* Default to 10% from top, centered */
padding: 0;
border: 1px solid var(--le-border-color-dark, #ccc);
max-width: 600px;
border-radius: var(--le-border-radius-large, 8px);
box-shadow: var(--le-shadow-modal, 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19));
display: flex;
flex-direction: column;
align-self: flex-start;
}
/* Specific styles for modal mobile-view class */
.modal-shared-content.mobile-view {
width: 90%; /* Override any fixed width from shared styles */
margin: 0; /* Less margin from top on mobile */
max-width: 90%;
min-width: 280px !important;
}
.modal-shared-header {
padding: var(--le-padding-s, 0.75em) var(--le-padding-m, 1.25em); /* Increased padding */
border-bottom: 1px solid var(--le-border-color-medium, #eee);
font-weight: bold;
color: var(--le-text-color-primary, #333);
background-color: var(--le-background-color-header, #f9f9f9); /* Modal header distinct background */
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--le-font-size-large, 1.4em); /* Increased large font size */
border-top-left-radius: var(--le-border-radius-large, 8px); /* Match content radius */
border-top-right-radius: var(--le-border-radius-large, 8px); /* Match content radius */
}
.modal-shared-header .close-button-shared { /* Specific styling for a close button if needed */
color: var(--le-text-color-secondary, #aaa);
font-size: var(--le-font-size-xlarge, 1.8em); /* Increased size */
font-weight: bold;
background: none;
border: none;
cursor: pointer;
}
.modal-shared-header .close-button-shared:hover,
.modal-shared-header .close-button-shared:focus {
color: var(--le-text-color-primary, #000);
text-decoration: none;
}
.modal-shared-body {
padding: var(--le-padding-m, 1.25em); /* Increased padding */
overflow-y: auto; /* Allow body to scroll if content is too long */
flex-grow: 1; /* Allows body to take up available space if modal has fixed height */
}
.modal-shared-footer {
padding: var(--le-padding-m, 1.25em);
text-align: right;
border-top: 1px solid var(--le-border-color-medium, #eee);
background-color: var(--le-background-color-header, #f9f9f9); /* Optional: footer background */
border-bottom-left-radius: var(--le-border-radius-large, 8px); /* Match content radius */
border-bottom-right-radius: var(--le-border-radius-large, 8px); /* Match content radius */
}
.modal-shared-footer .button-shared + .button-shared { /* Spacing between buttons in footer */
margin-left: var(--le-padding-s, 0.75em); /* Increased margin */
}
`;
const formStyles = `
.form-group-shared {
margin-bottom: var(--le-padding-m, 1.25em); /* Increased margin */
}
.form-label-shared {
display: block;
margin-bottom: var(--le-padding-xs, 0.4em); /* Increased margin */
font-weight: bold;
color: var(--le-text-color-primary, #333);
font-size: var(--le-font-size-label, 1em)); /* Use variable with fallback */
}
.form-input-shared,
.form-select-shared {
width: 100%;
height: auto;
padding: var(--le-padding-s, 0.75em);
border: 1px solid var(--le-border-color-dark, #ccc);
border-radius: var(--le-border-radius-standard, 4px);
box-sizing: border-box;
font-size: var(--le-font-size-input, 1em)); /* Use variable with fallback */
color: var(--le-text-color-primary, #333);
background-color: var(--le-background-color-panel, #fff);
}
/* Ensure date inputs have consistent styling across browsers */
.form-input-shared[type="date"] {
max-width: 100%;
}
.form-input-shared:focus,
.form-select-shared:focus {
border-color: var(--le-border-color-accent, #2196f3);
outline: none; /* Or a custom focus ring */
box-shadow: 0 0 0 2px var(--le-focus-ring-color, rgba(33, 150, 243, 0.3));
}
/* Specific styling for checkbox groups if needed */
.form-checkbox-label-shared {
display: flex; /* Changed to flex for better alignment */
align-items: center;
font-weight: normal; /* Typically labels for checkboxes are not bold by default */
color: var(--le-text-color-primary, #333);
}
.form-checkbox-label-shared input[type="checkbox"] {
margin-right: var(--le-padding-s, 0.75em); /* Increased margin */
/* Consider custom styling for checkboxes if desired, or rely on browser defaults */
/* For consistent appearance across browsers, custom checkbox styling can be complex */
/* For now, using default with adjusted margin */
width: auto; /* Override width: 100% from .form-input-shared if a generic class was applied */
vertical-align: middle; /* Align checkbox with text */
}
/* Styling for a container of multiple checkboxes or radio buttons */
.form-options-group-shared {
/* Styles for a group of checkboxes/radios, e.g., display: flex; flex-direction: column; gap: ... */
}
/* Styling for individual option within a group */
.form-option-item-shared {
/* Styles for each checkbox/radio item within a group */
}
/* Enhanced checkbox styles for better mobile usability */
.checkbox-enhanced-shared {
position: relative;
display: inline-block;
cursor: pointer;
user-select: none;
margin-right: var(--le-padding-s, 0.75em);
}
.checkbox-enhanced-shared input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkbox-enhanced-shared .checkmark-shared {
position: relative;
display: inline-block;
width: 1.5em;
height: 1.5em;
background-color: var(--le-background-color-panel, #fff);
border: 2px solid var(--le-border-color-dark, #ccc);
border-radius: var(--le-border-radius-small, 3px);
transition: all 0.2s ease;
vertical-align: middle;
margin-right: var(--le-padding-xs, 0.25em);
}
/* Mobile-specific larger checkboxes */
@media (max-width: 768px) {
.checkbox-enhanced-shared .checkmark-shared {
width: 2em;
height: 2em;
border-width: 2px;
}
}
/* Hover state */
.checkbox-enhanced-shared:hover input[type="checkbox"] ~ .checkmark-shared {
border-color: var(--le-text-color-accent, #2196f3);
background-color: var(--le-background-color-row-hover, #f9f9f9);
}
/* Focus state */
.checkbox-enhanced-shared input[type="checkbox"]:focus ~ .checkmark-shared {
border-color: var(--le-text-color-accent, #2196f3);
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
}
/* Checked state */
.checkbox-enhanced-shared input[type="checkbox"]:checked ~ .checkmark-shared {
background-color: var(--le-text-color-accent, #2196f3);
border-color: var(--le-text-color-accent, #2196f3);
}
/* Checkmark icon */
.checkbox-enhanced-shared .checkmark-shared:after {
content: "";
position: absolute;
display: none;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(45deg);
width: 0.4em;
height: 0.8em;
border: solid var(--le-text-color-on-primary, #fff);
border-width: 0 0.15em 0.15em 0;
}
/* Mobile-specific larger checkmark */
@media (max-width: 768px) {
.checkbox-enhanced-shared .checkmark-shared:after {
width: 0.5em;
height: 1em;
border-width: 0 0.2em 0.2em 0;
}
}
/* Show checkmark when checked */
.checkbox-enhanced-shared input[type="checkbox"]:checked ~ .checkmark-shared:after {
display: block;
}
/* Disabled state */
.checkbox-enhanced-shared input[type="checkbox"]:disabled ~ .checkmark-shared {
background-color: var(--le-background-color-button-disabled, #eee);
border-color: var(--le-border-color-medium, #ddd);
cursor: not-allowed;
}
.checkbox-enhanced-shared input[type="checkbox"]:disabled ~ .checkmark-shared:after {
border-color: var(--le-text-color-secondary, #aaa);
}
/* Legend/checkbox list styles for responsive layouts */
.checkbox-list-responsive-shared {
display: grid;
gap: var(--le-padding-s, 0.75em);
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
/* Mobile-specific responsive checkbox list */
@media (max-width: 768px) {
.checkbox-list-responsive-shared {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: var(--le-padding-m, 1em);
}
}
/* Extra small screens - force 2 columns */
@media (max-width: 480px) {
.checkbox-list-responsive-shared {
grid-template-columns: 1fr 1fr;
gap: var(--le-padding-s, 0.75em);
}
}
/* Legend item styling for checkbox lists */
.legend-item-shared {
display: flex;
align-items: center;
font-size: var(--le-font-size-small, 0.9em);
padding: var(--le-padding-xs, 0.25em);
border-radius: var(--le-border-radius-small, 3px);
transition: background-color 0.2s ease;
}
.legend-item-shared:hover {
background-color: var(--le-background-color-row-hover, #f9f9f9);
}
/* Mobile-specific legend item styling */
@media (max-width: 768px) {
.legend-item-shared {
font-size: var(--le-font-size-medium, 1.2em);
padding: var(--le-padding-s, 0.75em) var(--le-padding-xs, 0.25em);
min-height: 3em;
}
}
`;
const listStyles = `
.list-container {
max-height: 200px;
overflow-y: auto;
}
.list-panel {
border: 1px solid var(--le-border-color-medium, #ddd);
border-radius: var(--le-border-radius-standard, 4px);
background: var(--le-background-color-panel, #fff);
}
.list-panel h4 {
margin: 0;
padding: var(--le-padding-m, 1rem);
background: var(--le-background-color-header, #f8f9fa);
border-bottom: 1px solid var(--le-border-color-light, #eee);
color: var(--le-text-color-primary, #333);
}
.list-panel.disabled {
pointer-events: none;
opacity: 0.6;
}
.list {
list-style: none;
margin: 0;
padding: 0;
}
.list-item{
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--le-padding-xs) var(--le-padding-s);
border-bottom: 1px solid var(--le-border-color-light, #eee);
cursor: pointer;
transition: background-color 0.2s ease;
min-height: 2.5rem; /* Ensure consistent height for action buttons */
box-sizing: border-box;
}
.list-panel.disabled .list-item {
cursor: not-allowed;
}
.list-item:last-child {
border-bottom: none;
}
/* Example of a text part within a list item that should grow */
.list-item .list-item-text-primary {
flex-grow: 1;
font-size: var(--le-font-size-medium, 1.2em);
text-align: left; /* Ensure text is left-aligned */
display: flex;
align-items: center; /* Center text vertically within its container */
}
/* Example of an actions container part within a list item */
.list-item .list-item-actions {
flex-shrink: 0; /* Prevent actions from shrinking */
display: flex;
gap: var(--le-padding-xs, 0.4em); /* Increased gap */
align-items: center;
min-height: 2rem; /* Reserve space for buttons even when empty */
}
.list-item:hover {
background-color: var(--le-background-color-hover, #f5f5f5);
}
.list-item.selected {
background-color: var(--le-background-color-selected, #e3f2fd);
border-left: 3px solid var(--le-border-color-primary, #2196f3);
animation: highlightItem 0.6s ease-out;
}
@keyframes highlightItem {
0% {
background-color: var(--le-background-color-accent, #108CFF);
box-shadow: 0 0 0 2px rgba(16, 140, 255, 0.3), 0 2px 8px rgba(16, 140, 255, 0.2);
}
100% {
background-color: var(--le-background-color-selected, #e3f2fd);
box-shadow: none;
}
}
/* Mobile-specific list item adjustments */
@media (max-width: 768px) {
.list-item {
min-height: 4rem; /* Slightly taller for mobile touch targets */
padding: 0 var(--le-padding-xs, 1rem);;
}
.list-item .list-item-actions {
min-height: 2.5rem; /* Larger touch targets for mobile */
}
.list-item .list-item-actions .button-shared {
min-height: 2.5rem; /* Ensure buttons are touch-friendly */
}
}
`;
const tabStyles = `
/* Tab System Styles */
.modal-tabs-container {
display: flex;
flex-direction: column;
height: 100%;
}
.tab-navigation {
display: flex;
border-bottom: 2px solid var(--le-border-color-light);
margin-bottom: var(--le-padding-m);
gap: 0;
}
.tab-button {
background: var(--le-background-color-button);
border: 1px solid var(--le-border-color-medium);
border-bottom: none;
padding: var(--le-padding-s) var(--le-padding-m);
cursor: pointer;
font-weight: 500;
color: var(--le-text-color-secondary);
border-radius: var(--le-border-radius-standard) var(--le-border-radius-standard) 0 0;
position: relative;
transition: all 0.2s ease;
min-width: 120px;
text-align: center;
user-select: none;
}
.tab-button:hover {
background: var(--le-background-color-button-hover);
color: var(--le-text-color-primary);
}
.tab-button.active {
background: var(--le-background-color-panel);
color: var(--le-text-color-primary);
font-weight: 600;
border-bottom: 2px solid var(--le-background-color-panel);
margin-bottom: -2px;
z-index: 1;
}
.tab-button:not(:last-child) {
border-right: none;
}
.tab-content-container {
flex: 1;
min-height: 300px;
}
.tab-content {
display: none;
animation: fadeIn 0.2s ease-in;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Enhanced fieldset styling for tabs */
.tab-content fieldset {
margin-bottom: var(--le-padding-m);
border: 1px solid var(--le-border-color-medium);
border-radius: var(--le-border-radius-standard);
padding: var(--le-padding-m);
background: var(--le-background-color-header);
}
.tab-content fieldset legend {
font-weight: 600;
color: var(--le-text-color-primary);
padding: 0 var(--le-padding-s);
font-size: var(--le-font-size-small, 0.8em);
}
.tab-content fieldset:last-child {
margin-bottom: 0;
}
/* Enhanced form styling within tabs */
.tab-content .form-group {
margin-bottom: var(--le-padding-m);
}
.tab-content .form-group:last-child {
margin-bottom: 0;
}
.tab-content .form-group label {
display: block;
margin-bottom: var(--le-padding-xs);
font-weight: 500;
color: var(--le-text-color-primary);
font-size: var(--le-font-size-small, 0.8em);
}
.tab-content .form-group input[type="text"],
.tab-content .form-group input[type="number"],
.tab-content .form-group select {
width: 100%;
padding: var(--le-padding-s);
border: 1px solid var(--le-border-color-dark);
border-radius: var(--le-border-radius-standard);
box-sizing: border-box;
font-size: var(--le-font-size-medium);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.tab-content .form-group input[type="text"]:focus,
.tab-content .form-group input[type="number"]:focus,
.tab-content .form-group select:focus {
outline: none;
border-color: var(--le-text-color-accent);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
}
.tab-content .form-group input[type="checkbox"] {
margin-right: var(--le-padding-s);
transform: scale(1.1);
}
/* Enhanced rink points settings */
.tab-content .rink-points-settings {
border: 1px dashed var(--le-border-color-dark);
padding: var(--le-padding-m);
margin-top: var(--le-padding-s);
background-color: var(--le-background-color-header);
border-radius: var(--le-border-radius-standard);
transition: opacity 0.3s ease;
}
.tab-content .rink-points-settings.disabled {
opacity: 0.6;
}
/* Responsive adjustments for tabs */
@media (max-width: 600px) {
.tab-button {
padding: var(--le-padding-xs) var(--le-padding-s);
min-width: 100px;
font-size: var(--le-font-size-small);
}
.form-group-grid {
grid-template-columns: 1fr;
gap: var(--le-padding-s);
}
.tab-content fieldset {
padding: var(--le-padding-s);
}
}
`;
const helpBannerStyles = `
/* Help Banner Styles */
.help-banner-shared {
background-color: var(--le-background-color-header, #f8f9fa);
padding: var(--le-padding-xs, 0.25rem) var(--le-padding-m, 1rem);
text-align: right;
font-size: var(--le-font-size-xs, 0.75em);
border-bottom: 1px solid var(--le-border-color-medium, #eee);
display: flex;
justify-content: flex-end;
align-items: center;
gap: var(--le-padding-s, 0.5rem);
}
.help-banner-shared a {
color: var(--le-text-color-accent, #2196f3);
text-decoration: none;
font-weight: 500;
}
.help-banner-shared a:hover {
text-decoration: underline;
}
.help-banner-shared .separator {
color: var(--le-text-color-secondary, #666);
margin: 0 var(--le-padding-xs, 0.25rem);
}
`;
const pagingStyles = `
/* Paging Controls Styles */
.paging-controls {
display: flex;
justify-content: flex-end;
gap: var(--le-padding-s, 0.5rem);
margin-top: var(--le-padding-s, 0.5rem);
padding: var(--le-padding-xs, 0.25rem) 0;
background-color: transparent;
border: none;
font-size: var(--le-font-size-xs, 0.75em);
}
.paging-btn {
background: var(--le-background-color-button, #f5f5f5);
border: 1px solid var(--le-border-color-dark, #ccc);
border-radius: var(--le-border-radius-small, 3px);
padding: var(--le-padding-xs, 0.2rem) var(--le-padding-s, 0.7rem);
cursor: pointer;
color: var(--le-text-color-primary, #333);
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
user-select: none;
}
.paging-btn:hover:not(:disabled) {
background: var(--le-background-color-button-hover, #e0e0e0);
border-color: var(--le-border-color-dark, #bbb);
}
.paging-btn:disabled {
background: var(--le-background-color-button-disabled, #eee);
color: var(--le-text-color-secondary, #aaa);
cursor: not-allowed;
opacity: 0.7;
}
`;
const BASE_STYLES$4 = `
${panelStyles}
${buttonStyles}
${listStyles}
${pagingStyles}
:host {
display: block;
font-family: var(--le-font-family-main, 'Open Sans', Helvetica, Arial, sans-serif);
box-sizing: border-box;
color: var(--le-text-color-primary, #333);
}
.match-item {
padding: var(--swal-padding-xs, 0.2rem) 0;
display: flex;
align-items: center;
gap: var(--le-padding-xs, 0.25em);
}
.match-date {
color: var(--le-text-color-secondary, #666);
margin-bottom: var(--le-padding-xs, 0.2em);
}
.error {
color: var(--le-text-color-error, #ff0000);
padding: var(--le-padding-s, 0.5rem);
background-color: var(--le-background-color-error, #fff0f0);
border-radius: var(--swal-border-radius-standard, 4px);
}
.warning-icon-future-result { color: var(--le-color-status-warning, #f39c12); }
.warning-icon-conflict { color: var(--le-color-status-conflict, #e67e22); }
.warning-icon-pending-result { color: var(--le-color-status-pending, #e74c3c); }
.warning-icon-no-date { color: var(--le-color-status-info, #2196f3); }
.warning-icon {
flex-shrink: 0;
}
`;
const MOBILE_STYLES$3 = `
${BASE_STYLES$4}
.match-item {
padding: var(--swal-padding-xs, 0.2rem) 0;
}
/* Mobile: Ensure no height constraints for dynamic content-based height */
.attention-matches {
max-height: none;
overflow-y: visible;
}
`;
const DESKTOP_STYLES$3 = `
${BASE_STYLES$4}
.match-item {
padding: var(--swal-padding-xs, 0.2rem) 0;
}
/* Desktop: Add height constraints if needed for space management */
.attention-matches {
max-height: 300px;
overflow-y: auto;
}
`;
const TEMPLATE$1 = `
<div class="attention-matches">
{{attentionMatches}}
</div>
<div class="paging-controls" id="attention-paging" {{showPaging}}>
<button class="paging-btn button-shared button" id="attention-prev" {{prevDisabled}}>< Prev</button>
<button class="paging-btn button-shared button" id="attention-next" {{nextDisabled}}>Next ></button>
</div>
`;
/**
* Safely register a custom element, avoiding duplicate registration errors
* in single-page applications where modules may be loaded multiple times.
*
* @param {string} tagName - The custom element tag name (e.g., 'league-element')
* @param {CustomElementConstructor} elementClass - The element class constructor
* @param {boolean} [logRegistration=false] - Whether to log successful registrations
*/
function safeDefine(tagName, elementClass, logRegistration = false) {
if (!customElements.get(tagName)) {
customElements.define(tagName, elementClass);
if (logRegistration) {
console.log(`[ElementRegistry] Registered custom element: ${tagName}`);
}
} else if (logRegistration) {
console.log(`[ElementRegistry] Custom element already registered: ${tagName}`);
}
}
// Define custom event types for the LeagueMatchesAttention element
class LeagueMatchesAttentionEvent extends CustomEvent {
constructor(detail) {
super('league-matches-attention-event', {
detail,
bubbles: true,
// Ensure event bubbles up through the DOM
composed: true,
// Ensure event crosses shadow DOM boundaries
cancelable: true // Make the event cancelable
});
}
}
/**
* Custom element to display matches requiring attention with paging.
*
* @element league-matches-attention
* @attr {string} data - JSON stringified League instance object
* @attr {boolean} [is-mobile] - Whether to use mobile styles
* @attr {string} [team-mapping] - JSON stringified array of {value, label} objects mapping team values to display names
*
* Emits 'league-matches-attention-event' with detail { type: 'matchClick', match }
*/
class LeagueMatchesAttention extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: 'open'
});
this.league = null; // Store the league instance instead of just matches
this.currentPage = 0;
this.itemsPerPage = 5;
this.teamMapping = {};
}
static get observedAttributes() {
return ['data', 'is-mobile', 'team-mapping'];
}
connectedCallback() {
if (this.hasAttribute('team-mapping')) {
this._setTeamMapping(this.getAttribute('team-mapping'));
}
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
if (name === 'data') {
this.loadData(newValue);
} else if (name === 'is-mobile') {
this.render();
} else if (name === 'team-mapping') {
this._setTeamMapping(newValue);
this.render(); // Re-render to apply new team names
}
}
_setTeamMapping(mappingData) {
try {
if (!mappingData) {
this.teamMapping = {};
return;
}
// Parse mapping from the attribute string
const dataObj = typeof mappingData === 'string' ? JSON.parse(mappingData) : mappingData;
// Handle either array of {_id, name} objects or direct _id -> name mapping object
if (Array.isArray(dataObj)) {
// Convert array of objects to a simple map
this.teamMapping = {};
dataObj.forEach(team => {
if (team._id) {
this.teamMapping[team._id] = team.name || team._id;
}
});
} else {
// Assume we already have a direct mapping object
this.teamMapping = dataObj;
}
} catch (error) {
console.error('[LeagueMatchesAttention] Error parsing team mapping:', error);
this.teamMapping = {};
}
}
// Add a utility method to get display name for a team
getTeamDisplayName(teamId) {
if (!teamId || !this.teamMapping) {
return teamId;
}
// With standardized team model, teamMapping is a simple object map of _id -> name
const teamName = this.teamMapping[teamId];
if (teamName) {
return teamName;
}
return teamId;
}
/**
* Loads and parses the league data.
* @param {string|Object} data - League instance or JSON string
*/
async loadData(data) {
try {
if (typeof data === 'string') {
const parsedData = JSON.parse(data);
// Import League class to create a proper instance
const {
League
} = await Promise.resolve().then(function () { return index; });
this.league = new League(parsedData);
} else if (data && typeof data === 'object') {
// If it's already a League instance, use it directly
if (data.getMatchesRequiringAttention && data.getConflictingMatchIds) {
this.league = data;
} else {
// It's plain data, create a League instance
const {
League
} = await Promise.resolve().then(function () { return index; });
this.league = new League(data);
}
} else {
this.league = null;
}
this.currentPage = 0;
this.render();
this.dispatchEvent(new LeagueMatchesAttentionEvent({
type: 'dataLoaded',
league: this.league
}));
} catch (error) {
const errorMessage = 'Failed to load league data for attention matches';
this.showError(errorMessage);
console.error('Error loading league data for attention matches:', error);
this.dispatchEvent(new LeagueMatchesAttentionEvent({
type: 'error',
message: errorMessage,
error
}));
}
}
/**
* Shows an error message in the component.
* @param {string} message
*/
showError(message) {
const content = this.shadow.querySelector('.attention-matches');
if (content) {
content.innerHTML = `<div class="error">${message}</div>`;
}
}
_hasNextPage() {
if (!this.league || typeof this.league.getMatchesRequiringAttention !== 'function') {
return false;
}
const list = this.league.getMatchesRequiringAttention();
return (this.currentPage + 1) * this.itemsPerPage < list.length;
}
/**
* Renders the list of matches requiring attention for the current page.
* @returns {string}
*/
renderAttentionMatches() {
if (!this.league || typeof this.league.getMatchesRequiringAttention !== 'function') {
return '<div class="no-data">No league data available</div>';
}
const matches = this.league.getMatchesRequiringAttention();
const start = this.currentPage * this.itemsPerPage;
const pageItems = matches.slice(start, start + this.itemsPerPage);
if (pageItems.length === 0) {
if (matches.length > 0) {
return '<div class="no-data">None</div>';
}
return '<div class="no-data">None</div>';
}
const today = new Date();
today.setHours(0, 0, 0, 0);
let conflictingIds = new Set();
if (typeof this.league.getConflictingMatchIds === 'function') {
conflictingIds = this.league.getConflictingMatchIds();
}
return pageItems.map(match => {
// Get display names for teams
const homeTeamId = match.homeTeam?._id;
const awayTeamId = match.awayTeam?._id;
const homeTeamDisplay = match.homeTeam?.name || this.getTeamDisplayName(homeTeamId);
const awayTeamDisplay = match.awayTeam?.name || this.getTeamDisplayName(awayTeamId);
let warningSymbol = '';
let warningClass = '';
let tooltipText = '';
if (match.result && match.date) {
const matchDate = new Date(match.date);
matchDate.setHours(0, 0, 0, 0);
if (matchDate > today) {
tooltipText = "Result entered for a future match date.";
warningSymbol = '⚠';
warningClass = 'warning-icon-future-result';
}
} else if (conflictingIds.has(match._id)) {
tooltipText = "Scheduling conflict on this date.";
warningSymbol = '⚠';
warningClass = 'warning-icon-conflict';
} else if (match.date && !match.result) {
const matchDate = new Date(match.date);
matchDate.setHours(0, 0, 0, 0);
if (matchDate < today) {
tooltipText = "Match date passed, result pending.";
warningSymbol = '⏳';
warningClass = 'warning-icon-pending-result';
}
} else if (!match.date && !match.result) {
tooltipText = "No date set for match";
warningSymbol = '📅';
warningClass = 'warning-icon-no-date';
}
const titleAttr = tooltipText ? ` title="${this.escapeHtml(tooltipText)}"` : '';
const dataAttr = tooltipText ? ` data-attention-reason="${this.escapeHtml(tooltipText)}"` : '';
const warningSpan = warningSymbol ? `<span class="warning-icon ${warningClass}" title="${this.escapeHtml(tooltipText)}">${warningSymbol}</span>` : '';
return `
<div class="match-item list-item">
${warningSpan}
<a href="#" class="match-link list-item-text-primary" data-match-id="${match._id}"${titleAttr}${dataAttr}>
${this.escapeHtml(homeTeamDisplay)} vs ${this.escapeHtml(awayTeamDisplay)}
</a>
</div>
`;
}).join('');
}
_fillTemplate(template) {
const showPaging = this.currentPage > 0 || this._hasNextPage();
return template.replace('{{attentionMatches}}', this.renderAttentionMatches()).replace('{{prevDisabled}}', this.currentPage === 0 ? 'disabled' : '').replace('{{nextDisabled}}', this._hasNextPage() ? '' : 'disabled').replace('{{showPaging}}', showPaging ? '' : 'style="display: none;"');
}
render() {
const isMobile = this.getAttribute('is-mobile') === 'true';
this.shadow.innerHTML = `
<style>${isMobile ? MOBILE_STYLES$3 : DESKTOP_STYLES$3}</style>
${this._fillTemplate(TEMPLATE$1)}
`;
this.setupEventListeners();
}
setupEventListeners() {
// Paging buttons
const prevBtn = this.shadow.querySelector('#attention-prev');
const nextBtn = this.shadow.querySelector('#attention-next');
if (prevBtn) {
prevBtn.onclick = () => {
if (this.currentPage > 0) {
this.currentPage--;
this.render();
}
};
}
if (nextBtn) {
nextBtn.onclick = () => {
if (this._hasNextPage()) {
this.currentPage++;
this.render();
}
};
}
// Match click handlers
const matchLinks = this.shadow.querySelectorAll('.match-link');
matchLinks.forEach((link, index) => {
link.onclick = e => {
e.preventDefault();
e.stopPropagation();
const matchId = link.dataset.matchId;
const attentionReason = link.dataset.attentionReason;
let match = null;
if (this.league && typeof this.league.getMatchesRequiringAttention === 'function') {
match = this.league.getMatchesRequiringAttention().find(m => m._id === matchId);
}
if (match) {
// Create and dispatch the event
const event = new LeagueMatchesAttentionEvent({
type: 'matchClick',
match: match,
attentionReason: attentionReason
});
const dispatchResult = this.dispatchEvent(event);
// If the event was canceled, log it
if (!dispatchResult) {
console.warn('[LeagueMatchesAttention] Event was canceled by a listener');
}
} else {
console.warn('[LeagueMatchesAttention] No match found for ID:', matchId);
}
};
});
// Add a global click handler to the shadow root to see if clicks are reaching it
this.shadow.addEventListener('click', e => {
if (e.target.classList.contains('match-link')) {
console.log('[LeagueMatchesAttention] Shadow root click handler - match link clicked');
}
}, {
capture: true
});
}
// Public API methods
setPage(pageNumber) {
if (pageNumber >= 0 && pageNumber !== this.currentPage) {
this.currentPage = pageNumber;
this.render();
}
}
// Helper method for HTML escaping
escapeHtml(unsafe = '') {
const str = String(unsafe);
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
}
// Register the custom element
safeDefine('league-matches-attention', LeagueMatchesAttention);
const BASE_STYLES$3 = `
${buttonStyles}
${modalStyles}
${formStyles}
:host {
/* Host itself might be the modal-shared-overlay or contain it */
/* If host is the overlay: */
display: none; /* Controlled by 'open' attribute/property */
position: fixed;
z-index: var(--le-z-index-modal, 1001); /* Higher than admin modal if stacked */
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: var(--le-background-color-modal-overlay, rgba(0,0,0,0.4));
/* Use flex to center the modal-shared-content if the host is the overlay */
align-items: center;
justify-content: center;
}
:host([open]) {
display: flex;
}
/* STYLES FOR .dialog-content, .dialog-header, .dialog-body, .dialog-footer REMOVED as they are covered by .modal-shared-* classes */
/* GENERAL FORM STYLES for .form-group, label, input, select REMOVED as they are covered by .form-*-shared classes */
/* Keep styles specific to leagueMatch.js */
.score-inputs {
display: flex;
align-items: center;
gap: var(--le-padding-s, 0.5em);
flex-wrap: wrap;
}
.score-inputs label {
margin-bottom: 0; /* Override if needed */
}
.score-inputs input[type="number"] {
width: 80px; /* Increased from 60px to show placeholders better */
flex: 1 1 60px;
}
/* Remove spinner buttons from number inputs */
.score-inputs input[type="number"]::-webkit-inner-spin-button,
.score-inputs input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.score-inputs input[type="number"] {
-moz-appearance: textfield; /* Firefox */
}
/* Rink Results Styles */
.rink-results-container {
margin-top: 1rem;
border: 1px solid var(--le-border-color, #ccc);
border-radius: var(--le-border-radius-standard);
padding: 0.75rem;
background-color: var(--le-background-color-light, #f9f9f9);
}
.rink-results-header, .rink-result-row, .rink-results-totals, .rink-points-totals {
display: grid;
grid-template-columns: 2fr 1.5fr 1.5fr; /* Rink Label, Home, Away */
gap: 0.4rem;
align-items: center;
padding: 0.3rem 0;