@session-services/web-elements 0.1.16 → 0.1.18

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.
Files changed (27) hide show
  1. package/README.md +20 -2
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/session-services-elements.cjs.js +1 -1
  4. package/dist/cjs/session-services-formatted-text.cjs.entry.js +1759 -0
  5. package/dist/cjs/session-services-ticketing.cjs.entry.js +14167 -215
  6. package/dist/collection/collection-manifest.json +1 -0
  7. package/dist/collection/components/session-services-formatted-text/session-services-formatted-text.css +135 -0
  8. package/dist/collection/components/session-services-formatted-text/session-services-formatted-text.js +162 -0
  9. package/dist/collection/components/session-services-ticketing/session-services-ticketing.css +175 -3
  10. package/dist/collection/components/session-services-ticketing/session-services-ticketing.js +50 -9
  11. package/dist/collection/index.js +1 -11
  12. package/dist/components/session-services-formatted-text.d.ts +11 -0
  13. package/dist/components/session-services-formatted-text.js +1786 -0
  14. package/dist/components/session-services-ticketing.js +14189 -236
  15. package/dist/esm/loader.js +1 -1
  16. package/dist/esm/session-services-elements.js +1 -1
  17. package/dist/esm/session-services-formatted-text.entry.js +1757 -0
  18. package/dist/esm/session-services-ticketing.entry.js +14186 -234
  19. package/dist/session-services-elements/p-62a8e2cc.entry.js +1 -0
  20. package/dist/session-services-elements/p-a2404c46.entry.js +1 -0
  21. package/dist/session-services-elements/session-services-elements.esm.js +1 -1
  22. package/dist/types/components/session-services-formatted-text/session-services-formatted-text.d.ts +19 -0
  23. package/dist/types/components/session-services-ticketing/session-services-ticketing.d.ts +6 -0
  24. package/dist/types/components.d.ts +29 -0
  25. package/dist/types/index.d.ts +1 -0
  26. package/package.json +10 -5
  27. package/dist/session-services-elements/p-3090d137.entry.js +0 -1
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "entries": [
3
+ "components/session-services-formatted-text/session-services-formatted-text.js",
3
4
  "components/session-services-ticketing/session-services-ticketing.js"
4
5
  ],
5
6
  "compiler": {
@@ -0,0 +1,135 @@
1
+ :host {
2
+ /**
3
+ * Shadcn-compatible design tokens for formatted text component.
4
+ * Override these CSS custom properties to customize the appearance.
5
+ *
6
+ * @prop --foreground: Primary text color.
7
+ * @prop --muted-foreground: Secondary/muted text color.
8
+ * @prop --primary: Link color.
9
+ * @prop --border: Divider color.
10
+ * @prop --ring: Focus ring color.
11
+ * @prop --radius: Border radius.
12
+ * @prop --spacing-md: Medium spacing (1rem).
13
+ * @prop --spacing-lg: Large spacing (1.5rem).
14
+ * @prop --font-family: Base font family.
15
+ * @prop --transition-fast: Fast transition duration.
16
+ */
17
+
18
+ /* Light theme defaults (shadcn zinc) */
19
+ --foreground: oklch(0.145 0 0);
20
+ --muted-foreground: oklch(0.556 0 0);
21
+ --primary: oklch(0.42 0.24 264.376);
22
+ --border: oklch(0.922 0 0);
23
+ --ring: oklch(0.708 0 0);
24
+ --radius: 0.5rem;
25
+ --spacing-md: 1rem;
26
+ --spacing-lg: 1.5rem;
27
+ --font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
28
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
29
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
30
+
31
+ /* Internal tokens with external override fallbacks */
32
+ --internal-foreground: var(--foreground, oklch(0.145 0 0));
33
+ --internal-muted-foreground: var(--muted-foreground, oklch(0.556 0 0));
34
+ --internal-primary: var(--primary, oklch(0.42 0.24 264.376));
35
+ --internal-border: var(--border, oklch(0.922 0 0));
36
+ --internal-ring: var(--ring, oklch(0.708 0 0));
37
+ --internal-radius: var(--radius, 0.5rem);
38
+ --internal-spacing-md: var(--spacing-md, 1rem);
39
+ --internal-spacing-lg: var(--spacing-lg, 1.5rem);
40
+ --internal-font-family: var(
41
+ --font-family,
42
+ ui-sans-serif,
43
+ system-ui,
44
+ -apple-system,
45
+ BlinkMacSystemFont,
46
+ "Segoe UI",
47
+ Roboto,
48
+ "Helvetica Neue",
49
+ Arial,
50
+ "Noto Sans",
51
+ sans-serif
52
+ );
53
+ --internal-transition-fast: var(
54
+ --transition-fast,
55
+ 150ms cubic-bezier(0.4, 0, 0.2, 1)
56
+ );
57
+
58
+ display: block;
59
+ font-family: var(--internal-font-family);
60
+ line-height: 1.6;
61
+ color: var(--internal-foreground);
62
+ }
63
+
64
+ :host([data-theme="dark"]) {
65
+ --foreground: oklch(0.96 0 0);
66
+ --muted-foreground: oklch(0.78 0 0);
67
+ --primary: oklch(0.623 0.214 259.815);
68
+ --border: oklch(0.29 0 0);
69
+ --ring: oklch(0.6 0 0);
70
+ }
71
+
72
+ /* Reset box model */
73
+ * {
74
+ box-sizing: border-box;
75
+ margin: 0;
76
+ padding: 0;
77
+ }
78
+
79
+ *:focus:not(:focus-visible) {
80
+ outline: none;
81
+ }
82
+
83
+ *:focus-visible {
84
+ outline: 2px solid var(--internal-ring);
85
+ outline-offset: 2px;
86
+ border-radius: calc(var(--internal-radius) - 4px);
87
+ }
88
+
89
+ .formatted-text {
90
+ color: var(--internal-foreground);
91
+ white-space: pre-wrap;
92
+ word-wrap: break-word;
93
+ }
94
+
95
+ .formatted-link {
96
+ color: var(--internal-primary);
97
+ text-decoration: none;
98
+ transition: color var(--internal-transition-fast);
99
+ }
100
+
101
+ .formatted-link:hover {
102
+ color: oklch(from var(--internal-primary) calc(l + 0.1) c h);
103
+ text-decoration: underline;
104
+ }
105
+
106
+ .formatted-divider {
107
+ height: 0;
108
+ border: none;
109
+ border-top: 1px solid var(--internal-border);
110
+ margin: var(--internal-spacing-md) 0;
111
+ }
112
+
113
+ .soundcloud-embed {
114
+ margin: var(--internal-spacing-md) 0;
115
+ border-radius: var(--internal-radius);
116
+ overflow: hidden;
117
+ }
118
+
119
+ .soundcloud-embed iframe {
120
+ display: block;
121
+ border: none;
122
+ }
123
+
124
+ /* Screen reader only - visually hidden but accessible */
125
+ .sr-only {
126
+ position: absolute;
127
+ width: 1px;
128
+ height: 1px;
129
+ padding: 0;
130
+ margin: -1px;
131
+ overflow: hidden;
132
+ clip: rect(0, 0, 0, 0);
133
+ white-space: nowrap;
134
+ border: 0;
135
+ }
@@ -0,0 +1,162 @@
1
+ import {
2
+ // biome-ignore lint/correctness/noUnusedImports: h is required by Stencil JSX transform
3
+ h, Host, } from "@stencil/core";
4
+ import { getDisplayUrl, isSoundCloudUrl, parseFormattedText, } from "@session-services/utils/formatting";
5
+ export class SessionServicesFormattedText {
6
+ text = "";
7
+ theme = "system";
8
+ currentTheme = "light";
9
+ segments = [];
10
+ mediaQueryList;
11
+ componentWillLoad() {
12
+ this.setupTheme();
13
+ this.parseText();
14
+ }
15
+ textChanged() {
16
+ this.parseText();
17
+ }
18
+ themeChanged() {
19
+ this.setupTheme();
20
+ }
21
+ disconnectedCallback() {
22
+ if (this.mediaQueryList) {
23
+ this.mediaQueryList.removeEventListener("change", this.handleSystemThemeChange);
24
+ }
25
+ }
26
+ setupTheme() {
27
+ if (this.mediaQueryList) {
28
+ this.mediaQueryList.removeEventListener("change", this.handleSystemThemeChange);
29
+ }
30
+ if (this.theme === "system") {
31
+ // Guard for testing environments (JSDOM) where matchMedia may not exist
32
+ if (typeof globalThis.matchMedia === "function") {
33
+ this.mediaQueryList = globalThis.matchMedia("(prefers-color-scheme: dark)");
34
+ this.currentTheme = this.mediaQueryList.matches ? "dark" : "light";
35
+ this.mediaQueryList.addEventListener("change", this.handleSystemThemeChange);
36
+ }
37
+ else {
38
+ this.currentTheme = "light";
39
+ }
40
+ }
41
+ else {
42
+ this.currentTheme = this.theme;
43
+ }
44
+ }
45
+ handleSystemThemeChange = (e) => {
46
+ if (this.theme === "system") {
47
+ this.currentTheme = e.matches ? "dark" : "light";
48
+ }
49
+ };
50
+ parseText() {
51
+ this.segments = parseFormattedText(this.text);
52
+ }
53
+ renderLink(segment, key) {
54
+ const displayText = segment.linkType === "url"
55
+ ? getDisplayUrl(segment.href || "")
56
+ : segment.content;
57
+ return (h("a", { key: key, href: segment.href, target: "_blank", rel: "noopener noreferrer", class: "formatted-link" }, displayText, h("span", { class: "sr-only" }, " (opens in new tab)")));
58
+ }
59
+ renderSoundCloudEmbed(url, key, label) {
60
+ if (!isSoundCloudUrl(url)) {
61
+ return this.renderLink({
62
+ type: "link",
63
+ content: url,
64
+ href: url,
65
+ linkType: "url",
66
+ }, key);
67
+ }
68
+ const embedUrl = `https://w.soundcloud.com/player/?url=${encodeURIComponent(url)}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false`;
69
+ // Extract path for accessible title (e.g., "artist/track-name" from URL)
70
+ const pathLabel = new URL(url).pathname.slice(1).replace(/-/g, " ");
71
+ const iframeTitle = `SoundCloud: ${label || pathLabel || url}`;
72
+ return (h("div", { key: key, class: "soundcloud-embed" }, h("iframe", { width: "100%", height: "166", scrolling: "no", allow: "autoplay", sandbox: "allow-scripts allow-same-origin allow-popups", src: embedUrl, title: iframeTitle, loading: "lazy" })));
73
+ }
74
+ renderSegment(segment, index) {
75
+ switch (segment.type) {
76
+ case "text":
77
+ return h("span", { key: index }, segment.content);
78
+ case "link":
79
+ return this.renderLink(segment, index);
80
+ case "divider":
81
+ return h("hr", { key: index, class: "formatted-divider" });
82
+ case "soundcloud":
83
+ return this.renderSoundCloudEmbed(segment.href || "", index, segment.content);
84
+ default:
85
+ return null;
86
+ }
87
+ }
88
+ render() {
89
+ return (h(Host, { key: '1a7f36efb8cdbd1efdaa97c36b60593a13c99c4e', "data-theme": this.currentTheme }, h("div", { key: 'fce1176ff0ef2ea5c3f1b282ab9a52554491f9bf', class: "formatted-text" }, this.segments.map((segment, index) => this.renderSegment(segment, index)))));
90
+ }
91
+ static get is() { return "session-services-formatted-text"; }
92
+ static get encapsulation() { return "shadow"; }
93
+ static get originalStyleUrls() {
94
+ return {
95
+ "$": ["session-services-formatted-text.css"]
96
+ };
97
+ }
98
+ static get styleUrls() {
99
+ return {
100
+ "$": ["session-services-formatted-text.css"]
101
+ };
102
+ }
103
+ static get properties() {
104
+ return {
105
+ "text": {
106
+ "type": "string",
107
+ "mutable": false,
108
+ "complexType": {
109
+ "original": "string",
110
+ "resolved": "string",
111
+ "references": {}
112
+ },
113
+ "required": false,
114
+ "optional": false,
115
+ "docs": {
116
+ "tags": [],
117
+ "text": ""
118
+ },
119
+ "getter": false,
120
+ "setter": false,
121
+ "reflect": false,
122
+ "attribute": "text",
123
+ "defaultValue": "\"\""
124
+ },
125
+ "theme": {
126
+ "type": "string",
127
+ "mutable": false,
128
+ "complexType": {
129
+ "original": "\"light\" | \"dark\" | \"system\"",
130
+ "resolved": "\"dark\" | \"light\" | \"system\"",
131
+ "references": {}
132
+ },
133
+ "required": false,
134
+ "optional": false,
135
+ "docs": {
136
+ "tags": [],
137
+ "text": ""
138
+ },
139
+ "getter": false,
140
+ "setter": false,
141
+ "reflect": false,
142
+ "attribute": "theme",
143
+ "defaultValue": "\"system\""
144
+ }
145
+ };
146
+ }
147
+ static get states() {
148
+ return {
149
+ "currentTheme": {},
150
+ "segments": {}
151
+ };
152
+ }
153
+ static get watchers() {
154
+ return [{
155
+ "propName": "text",
156
+ "methodName": "textChanged"
157
+ }, {
158
+ "propName": "theme",
159
+ "methodName": "themeChanged"
160
+ }];
161
+ }
162
+ }
@@ -19,6 +19,7 @@
19
19
  * @prop --border: Border color for cards/controls.
20
20
  * @prop --input: Input border color (reserved).
21
21
  * @prop --ring: Focus ring color.
22
+ * @prop --backdrop: Modal backdrop overlay color.
22
23
  * @prop --spacing-xs: Spacing scale (xs).
23
24
  * @prop --spacing-sm: Spacing scale (sm).
24
25
  * @prop --spacing-md: Spacing scale (md).
@@ -67,6 +68,9 @@
67
68
  /* Additional colors for component */
68
69
  --success: oklch(0.66 0.154 149.48);
69
70
  --warning: oklch(0.72 0.153 66.34);
71
+ --info: oklch(0.623 0.214 259.815);
72
+ --info-foreground: oklch(0.488 0.243 264.376);
73
+ --backdrop: oklch(0 0 0 / 0.32);
70
74
 
71
75
  /* Radius */
72
76
  --radius: 0.625rem;
@@ -133,6 +137,9 @@
133
137
 
134
138
  --internal-success: var(--success, oklch(0.66 0.154 149.48));
135
139
  --internal-warning: var(--warning, oklch(0.72 0.153 66.34));
140
+ --internal-info: var(--info, oklch(0.623 0.214 259.815));
141
+ --internal-info-foreground: var(--info-foreground, oklch(0.488 0.243 264.376));
142
+ --internal-backdrop: var(--backdrop, oklch(0 0 0 / 0.32));
136
143
 
137
144
  --internal-radius: var(--radius, 0.625rem);
138
145
 
@@ -220,6 +227,8 @@
220
227
  --border: oklch(0.29 0 0);
221
228
  --input: oklch(0.29 0 0);
222
229
  --ring: oklch(0.60 0 0);
230
+ --info: oklch(0.623 0.214 259.815);
231
+ --info-foreground: oklch(0.707 0.165 254.624);
223
232
  }
224
233
 
225
234
  /* Tickets Section */
@@ -236,7 +245,7 @@
236
245
  background: var(--card);
237
246
  border: 1px solid var(--border);
238
247
  border-radius: var(--radius);
239
- margin: 0 0 var(--spacing-lg) 0;
248
+ margin: 0 0 var(--spacing-md) 0;
240
249
  overflow: hidden;
241
250
  width: 100%;
242
251
  box-shadow: var(--shadow-sm);
@@ -274,6 +283,11 @@
274
283
  transition: all var(--internal-transition-base);
275
284
  }
276
285
 
286
+ .tickets-wrapper > .ticket-section:last-child,
287
+ .tickets-wrapper > .unsectioned-admissions:last-child .ticket-section:last-child {
288
+ margin-bottom: 0;
289
+ }
290
+
277
291
  .ticket-section:focus-within {
278
292
  box-shadow: var(--internal-shadow-md);
279
293
  }
@@ -413,16 +427,67 @@
413
427
  flex: 1;
414
428
  }
415
429
 
430
+ .admission-header {
431
+ display: flex;
432
+ justify-content: space-between;
433
+ align-items: flex-start;
434
+ gap: var(--internal-spacing-md);
435
+ margin-bottom: var(--internal-spacing-sm);
436
+ }
437
+
438
+ .admission-header-text {
439
+ flex: 1;
440
+ min-width: 0;
441
+ }
442
+
416
443
  .admission-title {
417
444
  color: var(--internal-foreground);
418
- font-size: var(--internal-font-size-lg); /* match Order summary */
445
+ font-size: var(--internal-font-size-lg);
419
446
  font-weight: var(--internal-font-weight-semibold);
420
- margin-bottom: var(--internal-spacing-sm);
447
+ margin-bottom: 0;
421
448
  display: flex;
422
449
  align-items: center;
423
450
  gap: var(--internal-spacing-sm);
424
451
  }
425
452
 
453
+ .admission-subtitle {
454
+ color: var(--internal-muted-foreground);
455
+ font-size: var(--internal-font-size-sm);
456
+ margin-top: var(--internal-spacing-xs);
457
+ line-height: 1.4;
458
+ }
459
+
460
+ /* More Info Badge */
461
+ .more-info-badge {
462
+ display: inline-flex;
463
+ align-items: center;
464
+ gap: calc(var(--internal-spacing-xs) * 1.5);
465
+ padding: calc(var(--internal-spacing-xs) * 1.5) var(--internal-spacing-sm);
466
+ background: oklch(from var(--internal-info) l c h / 0.08);
467
+ color: var(--internal-info-foreground);
468
+ border: none;
469
+ border-radius: calc(var(--internal-radius) - 2px);
470
+ font-size: var(--internal-font-size-xs);
471
+ font-weight: var(--internal-font-weight-medium);
472
+ cursor: pointer;
473
+ transition: all var(--internal-transition-fast);
474
+ white-space: nowrap;
475
+ flex-shrink: 0;
476
+ }
477
+
478
+ .more-info-badge:hover {
479
+ background: oklch(from var(--internal-info) l c h / 0.16);
480
+ }
481
+
482
+ .more-info-badge:focus-visible {
483
+ outline: 2px solid var(--internal-ring);
484
+ outline-offset: 2px;
485
+ }
486
+
487
+ .more-info-badge svg {
488
+ flex-shrink: 0;
489
+ }
490
+
426
491
  .admission-time {
427
492
  color: var(--muted-foreground);
428
493
  font-size: var(--font-size-sm);
@@ -877,3 +942,110 @@
877
942
  margin: var(--spacing-md) 0;
878
943
  }
879
944
  }
945
+
946
+ /* Modal Styles */
947
+ .modal-backdrop {
948
+ position: fixed;
949
+ inset: 0;
950
+ z-index: 50;
951
+ display: flex;
952
+ align-items: center;
953
+ justify-content: center;
954
+ padding: var(--internal-spacing-md);
955
+ background: var(--internal-backdrop);
956
+ backdrop-filter: blur(4px);
957
+ animation: modal-backdrop-fade-in var(--internal-transition-base) ease-out;
958
+ }
959
+
960
+ @keyframes modal-backdrop-fade-in {
961
+ from {
962
+ opacity: 0;
963
+ }
964
+ to {
965
+ opacity: 1;
966
+ }
967
+ }
968
+
969
+ .modal-content {
970
+ position: relative;
971
+ width: 100%;
972
+ max-width: 32rem;
973
+ max-height: calc(100vh - var(--internal-spacing-xl) * 2);
974
+ overflow-y: auto;
975
+ background: var(--internal-popover);
976
+ color: var(--internal-popover-foreground);
977
+ border: 1px solid var(--internal-border);
978
+ border-radius: var(--internal-radius);
979
+ padding: var(--internal-spacing-lg);
980
+ box-shadow: var(--internal-shadow-xl);
981
+ animation: modal-content-scale-in var(--internal-transition-base) ease-out;
982
+ }
983
+
984
+ @keyframes modal-content-scale-in {
985
+ from {
986
+ opacity: 0;
987
+ transform: scale(0.98);
988
+ }
989
+ to {
990
+ opacity: 1;
991
+ transform: scale(1);
992
+ }
993
+ }
994
+
995
+ .modal-close-btn {
996
+ position: absolute;
997
+ top: var(--internal-spacing-sm);
998
+ right: var(--internal-spacing-sm);
999
+ display: flex;
1000
+ align-items: center;
1001
+ justify-content: center;
1002
+ width: 1.75rem;
1003
+ height: 1.75rem;
1004
+ padding: 0;
1005
+ background: transparent;
1006
+ border: 1px solid transparent;
1007
+ border-radius: calc(var(--internal-radius) - 4px);
1008
+ color: var(--internal-muted-foreground);
1009
+ cursor: pointer;
1010
+ opacity: 0.72;
1011
+ transition: all var(--internal-transition-fast);
1012
+ }
1013
+
1014
+ .modal-close-btn:hover {
1015
+ opacity: 1;
1016
+ background: var(--internal-accent);
1017
+ }
1018
+
1019
+ .modal-close-btn:focus-visible {
1020
+ outline: 2px solid var(--internal-ring);
1021
+ outline-offset: 1px;
1022
+ opacity: 1;
1023
+ }
1024
+
1025
+ .modal-header {
1026
+ margin-bottom: var(--internal-spacing-md);
1027
+ padding-right: var(--internal-spacing-xl);
1028
+ }
1029
+
1030
+ .modal-title {
1031
+ font-size: var(--internal-font-size-xl);
1032
+ font-weight: var(--internal-font-weight-semibold);
1033
+ color: var(--internal-foreground);
1034
+ margin: 0;
1035
+ line-height: 1.2;
1036
+ }
1037
+
1038
+ .modal-subtitle {
1039
+ color: var(--internal-muted-foreground);
1040
+ font-size: var(--internal-font-size-sm);
1041
+ margin-top: var(--internal-spacing-xs);
1042
+ line-height: 1.4;
1043
+ }
1044
+
1045
+ .modal-description {
1046
+ color: var(--internal-foreground);
1047
+ font-size: var(--internal-font-size-base);
1048
+ line-height: 1.6;
1049
+ white-space: pre-wrap;
1050
+ margin: 0;
1051
+ }
@@ -5,6 +5,8 @@ import {
5
5
  h, Host, } from "@stencil/core";
6
6
  const CalendarIcon = () => (h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M8 2v4" }), h("path", { d: "M16 2v4" }), h("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }), h("path", { d: "M3 10h18" })));
7
7
  const LocationIcon = () => (h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" }), h("circle", { cx: "12", cy: "10", r: "3" })));
8
+ const InfoIcon = () => (h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("path", { d: "M12 16v-4" }), h("path", { d: "M12 8h.01" })));
9
+ const CloseIcon = () => (h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M18 6 6 18" }), h("path", { d: "M6 6l12 12" })));
8
10
  export class SessionsTicketing {
9
11
  tenantId;
10
12
  eventId;
@@ -23,6 +25,7 @@ export class SessionsTicketing {
23
25
  quoteError = null;
24
26
  checkoutLoading = false;
25
27
  currentTheme = "light";
28
+ openModalAdmissionId = null;
26
29
  checkoutInit;
27
30
  cartUpdate;
28
31
  errorOccurred;
@@ -43,6 +46,10 @@ export class SessionsTicketing {
43
46
  if (this.mediaQueryList) {
44
47
  this.mediaQueryList.removeEventListener("change", this.handleSystemThemeChange);
45
48
  }
49
+ // Clean up modal listener if component unmounts while modal is open
50
+ if (this.openModalAdmissionId) {
51
+ document.removeEventListener("keydown", this.handleModalKeyDown);
52
+ }
46
53
  }
47
54
  async initializeComponent() {
48
55
  this.error = null;
@@ -82,13 +89,16 @@ export class SessionsTicketing {
82
89
  trackEvent(eventType, properties = {}) {
83
90
  if (!this.analytics || !this.enableAnalytics)
84
91
  return;
92
+ // Ensure required fields exist before tracking
93
+ if (!this.event?.teamId || !this.event?.promoterId)
94
+ return;
85
95
  this.analytics.track(eventType, {
86
96
  event_id: this.eventId,
87
- team_id: this.event?.teamId,
88
- promoter_id: this.event?.promoterId,
89
- event_name: this.event?.name,
90
- event_slug: this.event?.slug,
91
- currency: this.event?.currency,
97
+ team_id: this.event.teamId,
98
+ promoter_id: this.event.promoterId,
99
+ event_name: this.event.name,
100
+ event_slug: this.event.slug,
101
+ currency: this.event.currency,
92
102
  ...properties,
93
103
  });
94
104
  }
@@ -370,6 +380,26 @@ export class SessionsTicketing {
370
380
  handleQuantityIncrease = async (admissionId, sectionId) => {
371
381
  await this.updateQuantity(admissionId, sectionId, 1);
372
382
  };
383
+ openModal = (admissionId, e) => {
384
+ e.stopPropagation();
385
+ this.openModalAdmissionId = admissionId;
386
+ document.addEventListener("keydown", this.handleModalKeyDown);
387
+ };
388
+ closeModal = () => {
389
+ this.openModalAdmissionId = null;
390
+ document.removeEventListener("keydown", this.handleModalKeyDown);
391
+ };
392
+ handleModalKeyDown = (e) => {
393
+ if (e.key === "Escape") {
394
+ this.closeModal();
395
+ }
396
+ };
397
+ handleBackdropClick = (e) => {
398
+ if (e.target instanceof HTMLElement &&
399
+ e.target.classList.contains("modal-backdrop")) {
400
+ this.closeModal();
401
+ }
402
+ };
373
403
  renderQuantityControls(admission, sectionId) {
374
404
  const key = `${admission.id}::${sectionId || "none"}`;
375
405
  const quantity = this.selectedItems.get(key) || 0;
@@ -395,7 +425,7 @@ export class SessionsTicketing {
395
425
  const ariaLabel = section
396
426
  ? `${admission.name} in ${section.name} - ${isSoldOut ? "Sold Out" : this.formatPriceValue(basePrice)}`
397
427
  : `${admission.name} - ${isSoldOut ? "Sold Out" : this.formatPriceValue(basePrice)}`;
398
- return (h("section", { class: `admission-card ${isSoldOut ? "sold-out" : ""} ${isStandaloneAdmission ? "solo-admission" : ""}`, key: key, tabindex: isSoldOut ? -1 : 0, "aria-label": ariaLabel }, h("div", { class: "admission-content" }, h("div", { class: "admission-details" }, h("h3", { class: "admission-title" }, admission.name), isStandaloneAdmission && (h(h.Fragment, null, this.renderEventTimeAndLocation(), this.renderPriceAndControls(admission, sectionId, isSoldOut))), section &&
428
+ return (h("section", { class: `admission-card ${isSoldOut ? "sold-out" : ""} ${isStandaloneAdmission ? "solo-admission" : ""}`, key: key, tabindex: isSoldOut ? -1 : 0, "aria-label": ariaLabel }, h("div", { class: "admission-content" }, h("div", { class: "admission-details" }, h("div", { class: "admission-header" }, h("div", { class: "admission-header-text" }, h("h3", { class: "admission-title" }, admission.name), admission.subtitle && (h("p", { class: "admission-subtitle" }, admission.subtitle))), admission.description && (h("button", { type: "button", class: "more-info-badge", onClick: (e) => this.openModal(admission.id, e), "aria-label": `More info about ${admission.name}` }, InfoIcon(), h("span", null, "More info")))), isStandaloneAdmission && (h(h.Fragment, null, this.renderEventTimeAndLocation(), this.renderPriceAndControls(admission, sectionId, isSoldOut))), section &&
399
429
  this.renderPriceAndControls(admission, sectionId, isSoldOut)))));
400
430
  }
401
431
  renderSectionHeader(section, isSectionSoldOut) {
@@ -456,6 +486,16 @@ export class SessionsTicketing {
456
486
  ? "Free"
457
487
  : this.formatPriceValue(this.quote.price?.total || 0))), h("button", { type: "button", class: "checkout-btn", onClick: () => this.initiateCheckout(), disabled: this.isCheckoutDisabled() }, this.getCheckoutButtonText())));
458
488
  }
489
+ renderModal() {
490
+ if (!this.openModalAdmissionId)
491
+ return null;
492
+ const admission = this.event?.admissions.find((a) => a.id === this.openModalAdmissionId);
493
+ if (!admission)
494
+ return null;
495
+ return (
496
+ // biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: backdrop click-to-close is a common modal pattern, keyboard handled via Escape key
497
+ h("div", { class: "modal-backdrop", onClick: this.handleBackdropClick }, h("div", { class: "modal-content", role: "dialog", "aria-modal": "true", "aria-labelledby": "modal-title" }, h("button", { type: "button", class: "modal-close-btn", onClick: () => this.closeModal(), "aria-label": "Close" }, CloseIcon()), h("div", { class: "modal-header" }, h("h2", { id: "modal-title", class: "modal-title" }, admission.name), admission.subtitle && (h("p", { class: "modal-subtitle" }, admission.subtitle))), h("p", { class: "modal-description" }, admission.description))));
498
+ }
459
499
  renderTicketSections() {
460
500
  const generalAdmissions = this.event?.admissions.filter((adm) => !adm.sectionId) || [];
461
501
  return (h("div", { class: "tickets-wrapper" }, generalAdmissions.length > 0 && (h("div", { class: "unsectioned-admissions" }, generalAdmissions
@@ -471,7 +511,7 @@ export class SessionsTicketing {
471
511
  return this.renderErrorState();
472
512
  if (!this.event)
473
513
  return this.renderNotFoundState();
474
- return (h(Host, { "data-theme": this.currentTheme }, h("div", { class: "session-services-ticketing-container" }, this.renderTicketSections(), this.renderOrderSummary())));
514
+ return (h(Host, { "data-theme": this.currentTheme }, h("div", { class: "session-services-ticketing-container" }, this.renderTicketSections(), this.renderOrderSummary()), this.renderModal()));
475
515
  }
476
516
  static get is() { return "session-services-ticketing"; }
477
517
  static get encapsulation() { return "shadow"; }
@@ -656,7 +696,8 @@ export class SessionsTicketing {
656
696
  "quoteLoading": {},
657
697
  "quoteError": {},
658
698
  "checkoutLoading": {},
659
- "currentTheme": {}
699
+ "currentTheme": {},
700
+ "openModalAdmissionId": {}
660
701
  };
661
702
  }
662
703
  static get events() {
@@ -687,7 +728,7 @@ export class SessionsTicketing {
687
728
  },
688
729
  "complexType": {
689
730
  "original": "OrderQuote",
690
- "resolved": "{ items: { id: string; kind: \"ADMISSION\" | \"ADDON\"; quantity: number; price: { net: number; total: number; gross: number; base: number; booking: number; refunded: number; discount: number; currency: string; }; admissionId?: string; sectionId?: string; admission?: { name: string; capacity: number; limit: number; order: number; entryStartsAt: string; entryEndsAt: string; saleStartsAt: string; saleEndsAt: string; price: { base: number; booking: number; currency: string; }; teamId: string; promoterId: string; eventId: string; eventSlug: string; id?: string; available?: number; sold?: number; reserved?: number; createdAt?: string; updatedAt?: string; sectionId?: string; }; section?: { name: string; capacity: number; limit: number; order: number; location: { createdAt: string; updatedAt: string; name: string; address: string; country: string; latitude: number; longitude: number; timeZone: string; id?: string; googlePlaceId?: string; city?: string; state?: string; postcode?: string; types?: string[]; description?: string; image?: { src: string; alt: string; width: number; height: number; }; venueId?: string; }; teamId: string; promoterId: string; eventId: string; eventSlug: string; id?: string; createdAt?: string; updatedAt?: string; available?: number; sold?: number; reserved?: number; }; }[]; price: { net: number; total: number; gross: number; base: number; booking: number; shipping: number; refunded: number; discount: number; gateway: number; dispute: number; currency: string; }; couponCode?: { name: string; active: boolean; couponId: string; teamId: string; id?: string; createdAt?: string; updatedAt?: string; uses?: number; }; }",
731
+ "resolved": "{ items: { id: string; kind: \"ADMISSION\" | \"ADDON\"; quantity: number; price: { net: number; total: number; gross: number; base: number; booking: number; refunded: number; discount: number; currency: string; }; admissionId?: string; sectionId?: string; admission?: { name: string; capacity: number; limit: number; order: number; entryStartsAt: string; entryEndsAt: string; saleStartsAt: string; saleEndsAt: string; price: { base: number; booking: number; currency: string; }; visibility: \"ALWAYS\" | \"STACKED\" | \"HIDDEN\"; teamId: string; promoterId: string; eventId: string; eventSlug: string; id?: string; subtitle?: string; description?: string; available?: number; sold?: number; reserved?: number; createdAt?: string; updatedAt?: string; passcode?: string; sectionId?: string; }; section?: { name: string; capacity: number; limit: number; order: number; location: { createdAt: string; updatedAt: string; name: string; address: string; country: string; latitude: number; longitude: number; timeZone: string; id?: string; googlePlaceId?: string; city?: string; state?: string; postcode?: string; types?: string[]; description?: string; image?: { src: string; alt: string; width: number; height: number; }; venueId?: string; }; teamId: string; promoterId: string; eventId: string; eventSlug: string; id?: string; createdAt?: string; updatedAt?: string; available?: number; sold?: number; reserved?: number; passcode?: string; }; }[]; price: { net: number; total: number; gross: number; base: number; booking: number; shipping: number; refunded: number; discount: number; gateway: number; dispute: number; currency: string; }; couponCode?: { name: string; active: boolean; couponId: string; teamId: string; id?: string; createdAt?: string; updatedAt?: string; uses?: number; }; }",
691
732
  "references": {
692
733
  "OrderQuote": {
693
734
  "location": "import",
@@ -1,11 +1 @@
1
- /**
2
- * @fileoverview entry point for your component library
3
- *
4
- * This is the entry point for your component library. Use this file to export utilities,
5
- * constants or data structure that accompany your components.
6
- *
7
- * DO NOT use this file to export your components. Instead, use the recommended approaches
8
- * to consume components of this package as outlined in the `README.md`.
9
- */
10
- // Export components through Stencil's loader mechanism
11
- // Types are exported through the generated dist/types folder
1
+ export {};