@meetelise/chat 1.12.0 → 1.12.3

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.
@@ -3,16 +3,19 @@ import { html, LitElement, TemplateResult } from "lit";
3
3
  import { customElement, property, state } from "lit/decorators.js";
4
4
  import { createRef, ref, Ref } from "lit/directives/ref.js";
5
5
  import { glowBarMp4, glowBarWebm } from "../assetUrls";
6
- import { inHousLauncherStyles } from "./inHouseLauncherStyles";
6
+ import { inHouseLauncherStyles } from "./inHouseLauncherStyles";
7
7
  import { EmailUsWindow, installEmailUsWindow } from "./actions/EmailUsWindow";
8
8
  import { installTextUsWindow, TextUsWindow } from "./actions/TextUsWindow";
9
9
  import { styleMap } from "lit/directives/style-map.js";
10
10
  import { classMap } from "lit/directives/class-map.js";
11
11
  import { installCallUsWindow } from "./actions/CallUsWindow";
12
+ import { getRegisteredPhoneNumbers } from "../getRegisteredPhoneNumbers";
13
+ import { TourScheduler } from "./Scheduler/tour-scheduler";
14
+ import { LabeledOption } from "../fetchBuildingInfo";
12
15
 
13
16
  @customElement("in-house-launcher")
14
17
  export class InHouseLauncher extends LitElement {
15
- static styles = inHousLauncherStyles;
18
+ static styles = inHouseLauncherStyles;
16
19
 
17
20
  @property({ type: Boolean })
18
21
  isMobile = false;
@@ -36,9 +39,13 @@ export class InHouseLauncher extends LitElement {
36
39
  @property({ type: Boolean })
37
40
  hasEmailEnabled = true;
38
41
  @property({ type: Boolean })
39
- hasSSTEnabled = false;
42
+ hasSSTEnabled = true;
40
43
  @property({ type: Boolean })
41
- hasTextUsEnabled = true;
44
+ hasTextUsEnabled = false;
45
+ @property({ attribute: false })
46
+ layoutOptions: LabeledOption[] = [];
47
+ @property({ attribute: false })
48
+ tourTypeOptions: LabeledOption[] = [];
42
49
  @property({ attribute: false })
43
50
  onChatTapped: () => void = () => {
44
51
  return;
@@ -64,10 +71,19 @@ export class InHouseLauncher extends LitElement {
64
71
 
65
72
  emailUsWindowRef: Ref<EmailUsWindow> = createRef();
66
73
  textUsWindowRef: Ref<TextUsWindow> = createRef();
74
+ tourSchedulerRef: Ref<TourScheduler> = createRef();
67
75
 
68
- updated = (): void => {
76
+ updated = async (): Promise<void> => {
69
77
  this.attachOnClickToEmailUsWindow();
70
78
  this.attachOnClickToTextUsWindow();
79
+ if (this.buildingId) {
80
+ const registeredPhoneNumbers = await getRegisteredPhoneNumbers(
81
+ this.buildingId
82
+ );
83
+ this.hasTextUsEnabled =
84
+ registeredPhoneNumbers.length > 0 && this.buildingId !== 4895;
85
+ }
86
+ this.attachOnClickToSSTWindow();
71
87
  };
72
88
 
73
89
  attachOnClickToEmailUsWindow = (): void => {
@@ -86,6 +102,14 @@ export class InHouseLauncher extends LitElement {
86
102
  textUsWindowRef.onCloseClicked = this.onCloseTextUsWindow;
87
103
  };
88
104
 
105
+ attachOnClickToSSTWindow = (): void => {
106
+ const sstWindowRef = this.tourSchedulerRef.value;
107
+ if (!sstWindowRef) {
108
+ return;
109
+ }
110
+ sstWindowRef.onCloseClicked = this.onCloseSSTWindow;
111
+ };
112
+
89
113
  onClickEmailOption = (e: MouseEvent): void => {
90
114
  e.preventDefault();
91
115
  e.stopPropagation();
@@ -138,7 +162,12 @@ export class InHouseLauncher extends LitElement {
138
162
  renderSSTOption = (): TemplateResult => {
139
163
  const text = this.getNumCallToActions() > 2 ? "Book" : "Book a tour";
140
164
  return html`
141
- <div class="in-house-launcher__call-to-action-option">${text}</div>
165
+ <div
166
+ class="in-house-launcher__call-to-action-option"
167
+ @click=${this.onClickSSTOption}
168
+ >
169
+ ${text}
170
+ </div>
142
171
  `;
143
172
  };
144
173
 
@@ -229,6 +258,16 @@ export class InHouseLauncher extends LitElement {
229
258
  ></text-us-window>
230
259
  </div>`
231
260
  : ""}
261
+ ${this.isSSTWindowOpen
262
+ ? html`<div class="in-house-launcher__window-wrapper">
263
+ <tour-scheduler
264
+ .layoutOptions=${this.layoutOptions}
265
+ .tourTypeOptions=${this.tourTypeOptions}
266
+ buildingId=${this.buildingId}
267
+ ${ref(this.tourSchedulerRef)}
268
+ ></tour-scheduler>
269
+ </div>`
270
+ : ""}
232
271
  ${this.isCallUsWindowOpen
233
272
  ? html`
234
273
  <div class="in-house-launcher__window-wrapper">
@@ -293,7 +332,7 @@ export class InHouseLauncher extends LitElement {
293
332
  />
294
333
  </svg>
295
334
  `,
296
- undefined,
335
+ this.onClickSSTOption,
297
336
  true
298
337
  )
299
338
  : ""}
@@ -368,6 +407,16 @@ export class InHouseLauncher extends LitElement {
368
407
  ></text-us-window>
369
408
  </div>`
370
409
  : ""}
410
+ ${this.isSSTWindowOpen
411
+ ? html`<div class="in-house-launcher__window-wrapper">
412
+ <tour-scheduler
413
+ .layoutOptions=${this.layoutOptions}
414
+ .tourTypeOptions=${this.tourTypeOptions}
415
+ buildingId=${this.buildingId}
416
+ ${ref(this.tourSchedulerRef)}
417
+ ></tour-scheduler>
418
+ </div>`
419
+ : ""}
371
420
  ${this.isCallUsWindowOpen && this.phoneNumber
372
421
  ? html`
373
422
  <div class="in-house-launcher__window-wrapper">
@@ -14,6 +14,7 @@ import { isMobile } from "../utils";
14
14
  import { installInHouseLauncher } from "./InHouseLauncher";
15
15
 
16
16
  import "./MEChat.css";
17
+ import { getRawAvailabilities } from "../getAvailabilities";
17
18
 
18
19
  export interface Options {
19
20
  building: string;
@@ -91,6 +92,7 @@ export class MEChat extends LitElement {
91
92
  return;
92
93
  }
93
94
  this.building = await fetchBuildingInfo(this.orgSlug, this.buildingSlug);
95
+ getRawAvailabilities(this.building.id); // we're not using this here, just want to cache the result
94
96
  this.chatId = getChatID(this.orgSlug, this.buildingSlug);
95
97
  this.avatarSrc = this.avatarSrc || this.building.avatarSrc;
96
98
  this.theme = getTheme(this.themeId ?? this.building.themeId);
@@ -243,6 +245,8 @@ export class MEChat extends LitElement {
243
245
  .isFirstMount=${!this.hasMounted}
244
246
  .isMini=${this.useMiniWidget}
245
247
  .buildingId=${this.building?.id ?? 0}
248
+ .layoutOptions=${this.building?.layoutOptions ?? []}
249
+ .tourTypeOptions=${this.building?.tourTypeOptions ?? []}
246
250
  phoneNumber="${this.building?.phoneNumber ?? ""}"
247
251
  textColor="${this.theme.chatHeader.textColor}"
248
252
  backgroundColor="${this.theme.chatPaneBackgroundColor}"
@@ -1,5 +1,10 @@
1
+ import { isSameMonth } from "date-fns";
2
+ import format from "date-fns/format";
3
+ import getDate from "date-fns/getDate";
4
+ import getMonth from "date-fns/getMonth";
5
+ import getYear from "date-fns/getYear";
1
6
  import { LitElement, html, TemplateResult, css } from "lit";
2
- import { property, state } from "lit/decorators.js";
7
+ import { customElement, property, state } from "lit/decorators.js";
3
8
  import { classMap } from "lit/directives/class-map.js";
4
9
  import {
5
10
  dayNames,
@@ -11,6 +16,7 @@ import {
11
16
  monthNames,
12
17
  } from "../utils";
13
18
 
19
+ @customElement("date-picker")
14
20
  export class DatePicker extends LitElement {
15
21
  /**
16
22
  * Optional attribute to set the date picker's default month.
@@ -26,16 +32,26 @@ export class DatePicker extends LitElement {
26
32
  @property({ attribute: "year", type: Number })
27
33
  defaultYear?: number;
28
34
 
29
- private _selectedDate?: number = undefined;
30
- set selectedDate(date: number | undefined) {
35
+ @property({ attribute: false })
36
+ availabilities?: {
37
+ [day: string]: Date[];
38
+ };
39
+
40
+ private _selectedDate?: Date = undefined;
41
+ set selectedDate(date: Date | undefined) {
31
42
  const old = this._selectedDate;
32
43
  this._selectedDate = date;
33
44
  this.requestUpdate("selectedDate", old);
34
- this.dispatchEvent(new Event("change"));
45
+ this.dispatchEvent(
46
+ new Event("change", {
47
+ composed: true,
48
+ bubbles: true,
49
+ })
50
+ );
35
51
  }
36
52
 
37
- @property({ type: Number })
38
- get selectedDate(): undefined | number {
53
+ @property({ attribute: false })
54
+ get selectedDate(): undefined | Date {
39
55
  return this._selectedDate;
40
56
  }
41
57
 
@@ -47,6 +63,7 @@ export class DatePicker extends LitElement {
47
63
  : this.now.getMonth();
48
64
 
49
65
  // Make it easy to increment/decrement `this.monthShown` at year boundaries
66
+ // TODO: `willUpdate` is better: see https://lit.dev/docs/components/properties/ and https://lit.dev/docs/components/lifecycle/#willupdate
50
67
  set monthShown(month: number) {
51
68
  const oldMonthShown = this._monthShown;
52
69
  if (month === 12) {
@@ -71,8 +88,6 @@ export class DatePicker extends LitElement {
71
88
  yearShown = this.defaultYear ?? this.now.getFullYear();
72
89
 
73
90
  static styles = css`
74
- @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap");
75
-
76
91
  :host {
77
92
  box-sizing: border-box;
78
93
  font-family: "Poppins";
@@ -81,6 +96,7 @@ export class DatePicker extends LitElement {
81
96
  }
82
97
 
83
98
  #calendar {
99
+ box-sizing: border-box;
84
100
  display: flex;
85
101
  flex-direction: column;
86
102
  user-select: none;
@@ -89,19 +105,21 @@ export class DatePicker extends LitElement {
89
105
  background: #e7e7e7;
90
106
  border: 1px solid #ffffff;
91
107
  border-radius: 10px;
92
- padding: 15px 12px 10px;
108
+ padding: 15px 12px 15px;
93
109
  }
94
110
 
95
111
  #header {
96
112
  display: flex;
97
113
  align-items: center;
98
114
  justify-content: space-between;
99
- margin-bottom: 9px;
115
+ margin-bottom: 13px;
100
116
  }
101
117
 
102
118
  h1 {
103
119
  font-weight: 600;
104
120
  font-size: 12px;
121
+ margin: 0;
122
+ margin-left: 0.7em;
105
123
  }
106
124
 
107
125
  #arrows {
@@ -153,8 +171,21 @@ export class DatePicker extends LitElement {
153
171
  transform: translate(-50%, -50%);
154
172
  z-index: -1;
155
173
  }
174
+ .dayNumber.today:not(.selected)::after {
175
+ content: "";
176
+ height: 23px;
177
+ aspect-ratio: 1;
178
+ border: 1px solid #202020;
179
+ border-radius: 50%;
180
+ position: absolute;
181
+ top: 50%;
182
+ left: 50%;
183
+ transform: translate(-50%, -50%);
184
+ z-index: 1;
185
+ }
156
186
  .dayNumber:not(.selected):hover::after {
157
187
  background-color: lightgray;
188
+ z-index: -1;
158
189
  }
159
190
  .dayNumber.selected::after {
160
191
  background-color: #202020;
@@ -166,15 +197,12 @@ export class DatePicker extends LitElement {
166
197
  .dayNumber.selected {
167
198
  color: white;
168
199
  }
169
-
170
- .dayNumber.differentMonth {
171
- color: #84838f;
200
+ .dayNumber:is(.differentMonth, .past, .noAvailabilities) {
201
+ opacity: 30%;
172
202
  }
173
203
  `;
174
204
 
175
205
  render(): TemplateResult {
176
- // TODO: check 0 vs. 1-indexing, make sure this works.
177
- // TODO: handle year boundaries, e.g. if it's January and last month was last year. also same for next month when switching months.
178
206
  const daysInMonth = getDaysInMonth(this.yearShown, this.monthShown);
179
207
  const monthStartDay =
180
208
  dayNames[getMonthStartDay(this.yearShown, this.monthShown)];
@@ -212,13 +240,40 @@ export class DatePicker extends LitElement {
212
240
  ];
213
241
 
214
242
  const dayElements = dayNums.map((dayNumber, index) => {
243
+ const isPast =
244
+ this.yearShown < this.now.getFullYear() ||
245
+ (this.yearShown === this.now.getFullYear() &&
246
+ this.monthShown < this.now.getMonth()) ||
247
+ (this.yearShown === this.now.getFullYear() &&
248
+ this.monthShown === this.now.getMonth() &&
249
+ dayNumber < this.now.getDate());
215
250
  const isDifferentMonth =
216
251
  index < extraDaysAtBeginningOfMonth.length ||
217
252
  index >= extraDaysAtBeginningOfMonth.length + daysOfMonth.length;
253
+ const isToday =
254
+ !isDifferentMonth &&
255
+ this.now.getMonth() === this.monthShown &&
256
+ this.now.getDate() === dayNumber;
257
+ const isSelected =
258
+ !!this.selectedDate &&
259
+ !isDifferentMonth &&
260
+ this.monthShown === getMonth(this.selectedDate) &&
261
+ this.yearShown === getYear(this.selectedDate) &&
262
+ dayNumber === getDate(this.selectedDate);
263
+ const dateString = format(
264
+ new Date(this.yearShown, this.monthShown, dayNumber),
265
+ "y-MM-dd"
266
+ );
267
+ const hasNoAvailabilities =
268
+ !this.availabilities?.[dateString] ||
269
+ this.availabilities?.[dateString]?.length === 0;
218
270
  return html`<span
219
271
  class="dayNumber ${classMap({
272
+ past: isPast,
220
273
  differentMonth: isDifferentMonth,
221
- selected: dayNumber === this.selectedDate && !isDifferentMonth,
274
+ today: isToday,
275
+ selected: isSelected,
276
+ noAvailabilities: hasNoAvailabilities,
222
277
  })}"
223
278
  >${dayNumber}</span
224
279
  >`;
@@ -232,8 +287,13 @@ export class DatePicker extends LitElement {
232
287
  <div
233
288
  id="arrows"
234
289
  @click="${(e: MouseEvent) => {
235
- // TODO: disable incrementing/decrementing to months with no appointments available
236
- if ((e.target as HTMLElement)?.closest("#back")) {
290
+ if (
291
+ (e.target as HTMLElement)?.closest("#back") &&
292
+ !isSameMonth(
293
+ this.now,
294
+ new Date(this.yearShown, this.monthShown, 1)
295
+ )
296
+ ) {
237
297
  this.monthShown--;
238
298
  }
239
299
  if ((e.target as HTMLElement)?.closest("#forward")) {
@@ -244,7 +304,13 @@ export class DatePicker extends LitElement {
244
304
  if (![" ", "Enter"].includes(e.key)) {
245
305
  return;
246
306
  }
247
- if ((e.target as HTMLElement)?.closest("#back")) {
307
+ if (
308
+ (e.target as HTMLElement)?.closest("#back") &&
309
+ !isSameMonth(
310
+ this.now,
311
+ new Date(this.yearShown, this.monthShown, 1)
312
+ )
313
+ ) {
248
314
  e.preventDefault();
249
315
  e.stopPropagation();
250
316
  this.monthShown--;
@@ -291,7 +357,11 @@ export class DatePicker extends LitElement {
291
357
  @click="${(e: MouseEvent) => {
292
358
  const target = e.target as HTMLElement | undefined;
293
359
  if (target?.closest("span.dayNumber:not(.differentMonth)"))
294
- this.selectedDate = parseInt(target.innerText);
360
+ this.selectedDate = new Date(
361
+ this.yearShown,
362
+ this.monthShown,
363
+ parseInt(target.innerText)
364
+ );
295
365
  }}"
296
366
  >
297
367
  <div class="row">
@@ -309,7 +379,6 @@ export class DatePicker extends LitElement {
309
379
  `;
310
380
  }
311
381
  }
312
- customElements.define("date-picker", DatePicker);
313
382
 
314
383
  const chunk = <T>(chunkLength: number, array: T[]) => {
315
384
  const chunks = [];
@@ -1,9 +1,8 @@
1
1
  import { LitElement, html, TemplateResult, css } from "lit";
2
- import { property, state, query } from "lit/decorators.js";
2
+ import { property, state, query, customElement } from "lit/decorators.js";
3
3
  import { classMap } from "lit/directives/class-map.js";
4
4
 
5
- // TODO: would be nice to mimic the usage of native <select> where the options are children
6
-
5
+ @customElement("me-select")
7
6
  export class MESelect extends LitElement {
8
7
  @property({ attribute: false })
9
8
  options: string[] = [];
@@ -12,10 +11,7 @@ export class MESelect extends LitElement {
12
11
  placeholder?: string = "Select";
13
12
 
14
13
  @property({ type: String })
15
- defaultOption?: string;
16
-
17
- @state()
18
- private selected?: string;
14
+ value?: string;
19
15
 
20
16
  @state()
21
17
  private activeOption: string | null = null;
@@ -23,7 +19,7 @@ export class MESelect extends LitElement {
23
19
  @state()
24
20
  private isOpen?: boolean = false;
25
21
 
26
- @query("#select")
22
+ @query("#select", true)
27
23
  meSelect!: HTMLDivElement;
28
24
 
29
25
  toggleSelect = (): void => {
@@ -34,11 +30,12 @@ export class MESelect extends LitElement {
34
30
  };
35
31
 
36
32
  setSelectedOption = (option: string, closeSelect = true): void => {
37
- this.selected = option;
33
+ this.value = option;
38
34
  if (closeSelect) {
39
35
  this.isOpen = !this.isOpen;
40
36
  this.activeOption = null;
41
37
  }
38
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
42
39
  };
43
40
 
44
41
  handleKeydown = (
@@ -69,8 +66,6 @@ export class MESelect extends LitElement {
69
66
  }
70
67
 
71
68
  static styles = css`
72
- @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap");
73
-
74
69
  :host {
75
70
  --light-grey: #e3e3e3;
76
71
  --active-option-color: #f5f7f9;
@@ -109,6 +104,7 @@ export class MESelect extends LitElement {
109
104
  position: absolute;
110
105
  min-height: 40px;
111
106
  max-width: 400px;
107
+ width: max-content;
112
108
  background-color: white;
113
109
  z-index: 20;
114
110
  margin-top: 4px;
@@ -199,7 +195,7 @@ export class MESelect extends LitElement {
199
195
  }
200
196
  }}"
201
197
  >
202
- <span id="selectText">${this.selected ?? this.placeholder}</span>
198
+ <span id="selectText">${this.value ?? this.placeholder}</span>
203
199
  <svg
204
200
  width="10"
205
201
  height="6"
@@ -241,4 +237,3 @@ export class MESelect extends LitElement {
241
237
  `;
242
238
  }
243
239
  }
244
- customElements.define("me-select", MESelect);
@@ -5,97 +5,144 @@ import { classMap } from "lit/directives/class-map.js";
5
5
  @customElement("time-picker")
6
6
  export class TimePicker extends LitElement {
7
7
  @property({ attribute: false })
8
- // options: string[] = [];
9
- options: string[] = [
10
- "9:10am",
11
- "10:00am",
12
- "11:00am",
13
- "1:00pm",
14
- "3:00pm",
15
- "3:30pm",
16
- "4:00pm",
17
- "4:30pm",
18
- "00:00pm",
19
- ];
8
+ options: string[] = [];
9
+
10
+ @property({ type: Boolean })
11
+ selectedDateExists = false;
12
+
13
+ @property({ type: Boolean })
14
+ horizontal = false;
20
15
 
21
16
  @state()
22
17
  private selected?: string;
23
18
 
19
+ @property({ attribute: false })
20
+ get selectedTime(): undefined | string {
21
+ return this.selected;
22
+ }
23
+
24
24
  static styles = css`
25
25
  * {
26
26
  box-sizing: border-box;
27
27
  }
28
28
 
29
+ :host {
30
+ color: #202020;
31
+ font-family: "Poppins";
32
+ }
33
+
29
34
  #optionContainer {
35
+ display: grid;
36
+ grid-template-columns: 85px 85px;
37
+ grid-auto-rows: 40px;
38
+ align-items: start;
30
39
  margin-left: 14px;
31
- display: flex;
32
- flex-wrap: wrap;
33
40
  column-gap: 14px;
34
41
  row-gap: 19px;
35
42
  min-width: 200px;
43
+ max-height: 310px;
44
+ overflow-y: auto;
45
+ }
46
+
47
+ #optionContainer.horizontal {
48
+ grid-template-columns: unset;
49
+ grid-auto-rows: unset;
50
+ max-height: unset;
51
+ overflow-y: unset;
52
+ grid-template-rows: 40px 40px;
53
+ grid-auto-columns: 85px;
54
+ grid-auto-flow: column;
55
+ row-gap: 13px;
56
+ column-gap: 14px;
57
+ max-width: 90%;
58
+ overflow-x: auto;
36
59
  }
37
60
 
38
61
  .option {
39
62
  position: relative;
40
- display: inline-block;
41
- /* padding: 11px 14px; */
63
+ display: inline-flex;
64
+ justify-content: center;
65
+ align-items: center;
42
66
  height: 40px;
43
- width: 85px;
44
- /* max-width: */
67
+ width: max-content;
68
+ padding: 11px 11px 11px 14px;
45
69
  background: #e7e7e7;
46
70
  border: 1px solid #ffffff;
47
71
  border-radius: 10px;
48
- color: #202020;
49
- font-family: "Poppins";
50
72
  font-style: normal;
51
- font-weight: 400;
52
73
  font-size: 14px;
53
- text-align: center;
54
74
  user-select: none;
55
75
  }
56
76
 
57
- .option > span {
58
- position: absolute;
59
- top: 50%;
60
- left: 50%;
61
- transform: translate(-50%, -50%);
62
- }
63
-
64
77
  .option.selected {
65
78
  color: white;
66
79
  background: #202020;
67
- border: 3px solid #83818e;
80
+ box-shadow: inset 0px 0px 0px 3px rgb(131 129 142);
68
81
  border-radius: 10px;
69
82
  }
83
+
84
+ #noAvailabilityText {
85
+ font-size: 12px;
86
+ margin-left: 20px;
87
+ line-height: 18px;
88
+ max-width: 170px;
89
+ }
90
+
91
+ #noAvailabilityText.horizontal {
92
+ margin-left: 0;
93
+ }
70
94
  `;
71
95
  render(): TemplateResult {
72
- return html`
73
- <div
74
- id="optionContainer"
75
- @click="${(e: MouseEvent) => {
76
- const target = e.target as HTMLElement | undefined;
77
- if (target?.closest(".option:not(.selected)"))
78
- this.selected = target.innerText;
79
- }}"
80
- @keydown="${(e: KeyboardEvent) => {
81
- const target = e.target as HTMLElement | undefined;
82
- if ([" ", "Enter"].includes(e.key) && target?.closest(".option")) {
83
- e.preventDefault();
84
- this.selected =
85
- target.innerText !== this.selected ? target.innerText : undefined;
86
- }
87
- }}"
88
- >
89
- ${this.options.map(
90
- (option) =>
91
- html`<div
92
- class="option ${classMap({ selected: this.selected === option })}"
93
- tabindex="0"
94
- >
95
- <span>${option}</span>
96
- </div>`
97
- )}
98
- </div>
99
- `;
96
+ return this.options?.length
97
+ ? html`
98
+ <div
99
+ id="optionContainer"
100
+ class=${this.horizontal ? "horizontal" : ""}
101
+ @click="${(e: MouseEvent) => {
102
+ const target = e.target as HTMLElement | undefined;
103
+ if (target?.closest(".option:not(.selected)"))
104
+ this.selected = target.innerText;
105
+ this.dispatchEvent(
106
+ new Event("change", { bubbles: true, composed: true })
107
+ );
108
+ }}"
109
+ @keydown="${(e: KeyboardEvent) => {
110
+ const target = e.target as HTMLElement | undefined;
111
+ if (
112
+ [" ", "Enter"].includes(e.key) &&
113
+ target?.closest(".option")
114
+ ) {
115
+ e.preventDefault();
116
+ this.selected =
117
+ target.innerText !== this.selected
118
+ ? target.innerText
119
+ : undefined;
120
+ this.dispatchEvent(
121
+ new Event("change", { bubbles: true, composed: true })
122
+ );
123
+ }
124
+ }}"
125
+ >
126
+ ${this.options.map(
127
+ (option) =>
128
+ html`<div
129
+ class="option ${classMap({
130
+ selected: this.selected === option,
131
+ })}"
132
+ tabindex="0"
133
+ >
134
+ <span>${option}</span>
135
+ </div>`
136
+ )}
137
+ </div>
138
+ `
139
+ : html` <div
140
+ id="noAvailabilityText"
141
+ class=${this.horizontal ? "horizontal" : ""}
142
+ >
143
+ ${this.selectedDateExists
144
+ ? html`<p>No available appointments for this day</p>`
145
+ : html`<p>Select a date</p>`}
146
+ </div>`;
100
147
  }
101
148
  }