1import { BaseTemplate } from './BaseTemplate.js'; 2 3/** 4 * <event-programming-schedule-template> â section layout for the event Schedule page. 5 * 6 * Mirrors the slot/responsive pattern of ApplicationListTemplate / ListPageTemplate 7 * but with extra rows for the search bar and the action toolbar (live toggle, add session, 8 * access-type / view-toggle dropdowns, share, print, options menu) and a conflict banner. 9 * 10 * Slots: 11 * banner â promo / contextual banner above the header 12 * header-title â <h1> 13 * watch-tutorial â tutorial link 14 * search â search input 15 * filter â filter button 16 * live-toggle â Draft/Live toggle 17 * add-session â primary "Add session" button 18 * access-type â public/private link-dropdown 19 * view-toggle â list/day link-dropdown 20 * share-embed â share icon 21 * print â print icon 22 * options â overflow ellipsis menu 23 * conflict-banner â conflict notification banner 24 * (default) â page content (calendar / list) 25 * 26 * The schedule page wraps its content in #body_wrapp / #page-contents which already 27 * provides the side-nav offset, so the BaseTemplate 300px host margin is overridden. 28 */ 29 30class EventProgrammingScheduleTemplate extends BaseTemplate { 31 constructor() { 32 super(); 33 } 34 35 connectedCallback() { 36 super.connectedCallback(); 37 38 this.updateResponsiveStyles(); 39 this.render(); 40 41 this._handleResizeBound = () => { 42 this.updateResponsiveStyles(); 43 }; 44 window.addEventListener('resize', this._handleResizeBound); 45 } 46 47 disconnectedCallback() { 48 window.removeEventListener('resize', this._handleResizeBound); 49 super.disconnectedCallback(); 50 } 51 52 // The schedule page's #body_wrapp already handles side-nav offset, so neutralize 53 // BaseTemplate's 300px host margin here. 54 updateLeftPosition() { 55 this.style.setProperty('margin-left', '0', 'important'); 56 } 57 58 render() { 59 const slotWrapper = this.getContentWrapper(); 60 if (!slotWrapper) return; 61 62 slotWrapper.innerHTML = ` 63 <style> 64 :host { 65 display: block; 66 padding: 16px !important; 67 box-sizing: border-box; 68 } 69 70 /* Replaces the legacy .schedule-manage-action-container hide/show â toggle the 71 hide-toolbar attribute on the host instead of class-toggling the wrapper. */ 72 :host([hide-toolbar]) .header-search-actions, 73 :host([hide-toolbar]) .tools-row { 74 display: none; 75 } 76 77 .template-header { 78 display: flex; 79 flex-direction: column; 80 } 81 82 .header-top { 83 display: flex; 84 justify-content: space-between; 85 align-items: center; 86 flex-wrap: wrap; 87 margin-bottom: 0.5rem; 88 } 89 90 /* Desktop: search/filter on the left, action buttons on the right â single row. 91 Mobile: stacks vertically, action buttons go full-width. */ 92 .header-search-actions { 93 display: flex; 94 align-items: center; 95 gap: 0.75rem; 96 margin-bottom: 0.75rem; 97 } 98 /* Section must actually grow â without flex-grow, search-bar-component's 99 width:100% resolves against the parent's content size and the input collapses. */ 100 .search-section { 101 flex: 1 1 auto; 102 max-width: 800px; 103 display: flex; 104 align-items: center; 105 gap: 0.5rem; 106 min-width: 0; 107 } 108 /* All buttons in the action cluster sit on the right on desktop. */ 109 .actions-section { 110 display: flex; 111 justify-content: flex-end; 112 align-items: center; 113 gap: 0.5rem; 114 flex: 0 0 auto; 115 margin-left: auto; 116 } 117 /* share-embed + options pair â sits at natural width on desktop, fills the row on mobile. */ 118 .share-embed-options { 119 display: flex; 120 align-items: center; 121 gap: 0.5rem; 122 } 123 ::slotted([slot="search"]) { flex: 1 1 auto; min-width: 0; max-width: 740px; } 124 ::slotted([slot="filter"]) { flex: 0 0 auto; } 125 ::slotted([slot="print"]) { 126 display: flex !important; 127 align-items: center; 128 justify-content: center; 129 align-self: center; 130 width: 32px; 131 height: 32px; 132 font-size: 20px; 133 border: 1px solid #000 !important; 134 border-radius: 4px; 135 box-sizing: border-box; 136 } 137 138 /* Utility row sitting between the conflict banner and the calendar content: 139 live-toggle + access-type / view-toggle dropdowns + print, right-aligned. */ 140 .tools-row { 141 display: flex; 142 justify-content: flex-end; 143 align-items: center; 144 gap: 0.5rem; 145 flex-wrap: wrap; 146 padding: 0.75rem 0; 147 } 148 149 ::slotted(h1[slot="header-title"]) { 150 color: #038580; 151 font-size: 32px; 152 font-weight: 500; 153 line-height: 40px; 154 margin: 0; 155 } 156 157 ::slotted(a[slot="watch-tutorial"]) { 158 display: inline-flex; 159 align-items: center; 160 font-size: 14px; 161 color: #026E80 !important; 162 text-decoration: none; 163 font-weight: 500; 164 gap: 4px; 165 white-space: nowrap; 166 line-height: 18px; 167 } 168 169 .template-content { padding: 0; } 170 171 @media (max-width: 768px) { 172 .header-top { 173 flex-direction: row; 174 justify-content: space-between; 175 gap: 0.5rem; 176 } 177 178 /* Stack search above actions. Within actions, +New session takes its own 179 full-width row; Share & embed + options share the next row. */ 180 .header-search-actions { 181 flex-direction: column; 182 align-items: stretch; 183 } 184 .actions-section { 185 flex-wrap: wrap; 186 width: 100%; 187 margin-left: 0; 188 justify-content: flex-start; 189 } 190 .actions-section ::slotted([slot="add-session"]) { 191 flex: 1 1 100%; 192 } 193 .share-embed-options { 194 flex: 1 1 100%; 195 width: 100%; 196 } 197 .share-embed-options ::slotted([slot="share-embed"]) { 198 flex: 1 1 auto; 199 } 200 .share-embed-options ::slotted([slot="options"]) { 201 flex: 0 0 auto; 202 } 203 } 204 </style> 205 206 <slot name="banner"></slot> 207 <header class="template-header"> 208 <div class="header-top"> 209 <slot name="header-title"></slot> 210 <slot name="watch-tutorial"></slot> 211 </div> 212 <div class="header-search-actions"> 213 <div class="search-section"> 214 <slot name="search"></slot> 215 <slot name="filter"></slot> 216 </div> 217 <div class="actions-section"> 218 <slot name="add-session"></slot> 219 <div class="share-embed-options"> 220 <slot name="share-embed"></slot> 221 <slot name="options"></slot> 222 </div> 223 </div> 224 </div> 225 </header> 226 <slot name="conflict-banner"></slot> 227 <div class="tools-row"> 228 <slot name="live-toggle"></slot> 229 <slot name="access-type"></slot> 230 <slot name="view-toggle"></slot> 231 <slot name="print"></slot> 232 </div> 233 <div class="template-content"> 234 <slot></slot> 235 </div> 236 `; 237 238 // Default content for an empty watch-tutorial link slot 239 requestAnimationFrame(() => { 240 const slottedLink = this.querySelector('a[slot="watch-tutorial"]'); 241 if (slottedLink && slottedLink.innerHTML.trim() === '') { 242 slottedLink.innerHTML = `<i class="uil uil-play-circle size-16"></i> Watch tutorial`; 243 } 244 }); 245 } 246 247 updateResponsiveStyles() { 248 // Mobile: +New session and Share & embed each take full-width on their own row. 249 // Desktop: both sit inline next to the search bar at their natural widths. 250 const isMobile = window.innerWidth <= 768; 251 ['add-session', 'share-embed'].forEach(slotName => { 252 const button = this.querySelector(`button-element[slot="${slotName}"], basic-button-element[slot="${slotName}"]`); 253 if (!button) return; 254 if (isMobile) button.setAttribute('full-width', ''); 255 else button.removeAttribute('full-width'); 256 }); 257 } 258} 259 260customElements.define('event-programming-schedule-template', EventProgrammingScheduleTemplate);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.