PageSourceSearch

https://www.eventeny.com/js/Layouts/TicketGroupSection.js?v=1790189618

js eventeny.com collected 2026-09-24 08:56:11 UTC 9,242 bytes, 238 lines download raw bytes

1/**
2 * @class TicketGroupSection
3 * @extends HTMLElement
4 *
5 * @classdesc Collapsible section wrapping a group of `<ticket-card-layout>` cards (see
6 * js/Layouts/TicketCard.js) under a titled, caret-toggled header. Purely a structural/display
7 * wrapper -- which tickets belong to which group, and in what order groups/sections appear on
8 * the page, is decided by the consuming page, not here. In particular this component keeps no
9 * internal registry keyed by `group-id`, so more than one section may share the same
10 * `group-id`/`group-title` on the same page without conflict -- a single logical group might
11 * need to render as more than one non-contiguous section if tickets end up sorted by something
12 * other than group (e.g. by name).
13 *
14 * @example
15 * <ticket-group-section group-id="4" group-title="VIP Access" default-open>
16 *   <ticket-card-layout ...></ticket-card-layout>
17 *   <ticket-card-layout ...></ticket-card-layout>
18 * </ticket-group-section>
19 *
20 * @attribute {string} group-id - Opaque id echoed back in the group-toggled event detail.
21 * @attribute {string} group-title - Header text.
22 * @attribute {boolean} default-open - Section starts expanded when present. Default: collapsed.
23 *
24 * @slot (default) - The group's `<ticket-card-layout>` children.
25 *
26 * @method setOpen(open) - Explicitly opens/collapses the section.
27 * @method isOpen() - Returns the current expanded state.
28 *
29 * @event group-toggled - Fired after the section's open state changes (header click, Enter/Space,
30 *                        or a setOpen() call). detail: {groupId, open}
31 */
32class TicketGroupSection extends HTMLElement {
33    static get observedAttributes() {
34        return ['group-title'];
35    }
36
37    constructor() {
38        super();
39        this.attachShadow({ mode: 'open' });
40
41        this._open = false;
42        this._contentId = `ticket-group-content-${Math.random().toString(36).slice(2, 9)}`;
43
44        const style = document.createElement('style');
45        style.textContent = `
46            @import url('https://unicons.iconscout.com/release/v4.0.8/css/line.css');
47
48            :host {
49                display: block;
50                font-family: 'Plus Jakarta Sans', 'Roboto', sans-serif;
51            }
52
53            .group-header {
54                all: unset;
55                box-sizing: border-box;
56                display: flex;
57                align-items: center;
58                justify-content: space-between;
59                width: 100%;
60                padding: 12px 4px;
61                cursor: pointer;
62                font-size: 18px;
63                font-weight: 600;
64                color: var(--text-primary, #222222);
65            }
66
67            .group-header:focus-visible {
68                outline: 2px solid var(--border-focused, #026E80);
69                outline-offset: 2px;
70                border-radius: 4px;
71            }
72
73            .group-caret {
74                display: flex;
75                align-items: center;
76                justify-content: center;
77                flex-shrink: 0;
78                transition: transform 0.2s ease;
79            }
80
81            .group-caret i {
82                font-size: 20px;
83                line-height: 1;
84            }
85
86            :host([open]) .group-caret {
87                transform: rotate(180deg);
88            }
89
90            /*
91             * Fixed large max-height (rather than measuring scrollHeight) is the same
92             * collapse-transition trick already used by js/Components/AccordionCheckbox.js --
93             * kept consistent with that existing pattern rather than introducing a second
94             * collapse technique into the codebase.
95             */
96            /*
97             * display:flex + gap (not ::slotted margin-top) spaces the cards -- a <slot>'s
98             * assigned nodes flatten into the flex layout as if they were direct children, so
99             * gap applies between them same as js/events/ticket-purchase-page.js's own
100             * #ticket-cards-root. This sidesteps the page-wide * {margin:0} reset in
101             * css/base-2017-05-15.css entirely instead of relying on ::slotted's specificity
102             * to beat it.
103             */
104            .group-content {
105                display: flex;
106                flex-direction: column;
107                gap: 16px;
108                max-height: 0;
109                overflow: hidden;
110                transition: max-height 0.3s ease-out;
111            }
112
113            .group-content.show {
114                max-height: 4000px;
115                transition: max-height 0.5s ease-in;
116            }
117
118            /*
119             * Mobile: the group becomes a full-bleed list block (its container,
120             * #ticket-cards-root, is made edge-to-edge at the page level for exactly this --
121             * see css/events/ticket.css). The header's hairline borders bracket the title and
122             * reach the true screen edges because they're drawn on .group-header itself, which
123             * is the full, unpadded width of :host -- .group-header's own padding sits INSIDE
124             * that border, so it never insets where the border line is drawn. gap drops to 0 here
125             * since the row spacing/divider between cards comes from each card's own
126             * ".ticket-row" wrapper below instead (a plain div the card itself is never aware of --
127             * see TicketListController.js's appendTicket()); cards keep their own real border only
128             * for selected/cart-added/error (js/Layouts/TicketCard.js's own mobile block).
129             */
130            @media (max-width: 640px) {
131                .group-header {
132                    padding: 12px 10px;
133                    border-top: 0.5px solid #C4CAC9;
134                    border-bottom: 0.5px solid #C4CAC9;
135                }
136
137                .group-content {
138                    gap: 0;
139                }
140
141                /*
142                 * !important beats css/base-2017-05-15.css's global *{border:0;margin:0;padding:0;}
143                 * / div{...} resets outright, rather than leaning on ::slotted(.ticket-row)'s own
144                 * (0,1,1) specificity to out-rank them across the shadow boundary.
145                 */
146                ::slotted(.ticket-row) {
147                    padding: 12px 8px !important;
148                    border-bottom: 0.5px solid #C4CAC9 !important;
149                }
150
151                ::slotted(.ticket-row:last-child) {
152                    border-bottom: none !important;
153                }
154            }
155        `;
156
157        const header = document.createElement('button');
158        header.type = 'button';
159        header.classList.add('group-header');
160        header.setAttribute('aria-expanded', 'false');
161        header.setAttribute('aria-controls', this._contentId);
162
163        this._titleEl = document.createElement('span');
164        this._titleEl.classList.add('group-title');
165
166        const caret = document.createElement('span');
167        caret.classList.add('group-caret');
168        caret.setAttribute('aria-hidden', 'true');
169        caret.innerHTML = `<i class="uil uil-angle-down"></i>`;
170
171        header.appendChild(this._titleEl);
172        header.appendChild(caret);
173
174        const content = document.createElement('div');
175        content.classList.add('group-content');
176        content.id = this._contentId;
177        content.appendChild(document.createElement('slot'));
178
179        this.shadowRoot.appendChild(style);
180        this.shadowRoot.appendChild(header);
181        this.shadowRoot.appendChild(content);
182
183        this._header = header;
184        this._content = content;
185
186        header.addEventListener('click', () => this.setOpen(!this._open));
187    }
188
189    connectedCallback() {
190        // createElement() runs the constructor before the caller has a chance to call
191        // setAttribute('default-open', ...), so the attribute can only be read reliably here,
192        // once the element has actually been inserted with all its attributes already set.
193        this._open = this.hasAttribute('default-open');
194        this._titleEl.textContent = this.getAttribute('group-title') || '';
195        this._applyOpenState({ silent: true });
196    }
197
198    attributeChangedCallback(name, oldValue, newValue) {
199        if (name === 'group-title' && this._titleEl) {
200            this._titleEl.textContent = newValue || '';
201        }
202    }
203
204    /**
205     * @param {boolean} open
206     */
207    setOpen(open) {
208        const next = !!open;
209        if (next === this._open) return;
210        this._open = next;
211        this._applyOpenState();
212    }
213
214    /**
215     * @returns {boolean}
216     */
217    isOpen() {
218        return this._open;
219    }
220
221    _applyOpenState({ silent = false } = {}) {
222        this.toggleAttribute('open', this._open);
223        this._header.setAttribute('aria-expanded', String(this._open));
224        this._content.classList.toggle('show', this._open);
225
226        if (!silent) {
227            this.dispatchEvent(new CustomEvent('group-toggled', {
228                detail: { groupId: this.getAttribute('group-id'), open: this._open },
229                bubbles: true,
230                composed: true
231            }));
232        }
233    }
234}
235
236if (!customElements.get('ticket-group-section')) {
237    customElements.define('ticket-group-section', TicketGroupSection);
238}

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.