@lifestreamdynamics/booking-widget 0.1.1 → 0.1.4

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.
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export declare class LsvBooking extends HTMLElement {
21
21
  private reset;
22
22
  private setLoading;
23
23
  private setError;
24
+ private getSignal;
24
25
  private loadSlots;
25
26
  private loadTimes;
26
27
  private submitBooking;
@@ -1,7 +1,7 @@
1
1
  var v = Object.defineProperty;
2
- var C = (h, p, e) => p in h ? v(h, p, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[p] = e;
3
- var d = (h, p, e) => C(h, typeof p != "symbol" ? p + "" : p, e);
4
- const k = `
2
+ var k = (h, u, e) => u in h ? v(h, u, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[u] = e;
3
+ var m = (h, u, e) => k(h, typeof u != "symbol" ? u + "" : u, e);
4
+ const C = `
5
5
  :host {
6
6
  display: block;
7
7
  font-family: system-ui, -apple-system, sans-serif;
@@ -393,22 +393,22 @@ const k = `
393
393
  gap: 0.5rem;
394
394
  margin-top: 1rem;
395
395
  }
396
- `, y = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"], x = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
396
+ `, y = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"], E = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
397
397
  class f extends HTMLElement {
398
398
  constructor() {
399
399
  super();
400
- d(this, "root");
401
- d(this, "abortController", null);
402
- d(this, "step", "slots");
403
- d(this, "slots", []);
404
- d(this, "selectedSlot", null);
405
- d(this, "selectedDate", "");
406
- d(this, "selectedTime", "");
407
- d(this, "availableTimes", []);
408
- d(this, "isLoading", !1);
409
- d(this, "errorMsg", "");
400
+ m(this, "root");
401
+ m(this, "abortController", null);
402
+ m(this, "step", "slots");
403
+ m(this, "slots", []);
404
+ m(this, "selectedSlot", null);
405
+ m(this, "selectedDate", "");
406
+ m(this, "selectedTime", "");
407
+ m(this, "availableTimes", []);
408
+ m(this, "isLoading", !1);
409
+ m(this, "errorMsg", "");
410
410
  // ── Event handling ────────────────────────────────────────────────────────
411
- d(this, "handleRootClick", (e) => {
411
+ m(this, "handleRootClick", (e) => {
412
412
  const r = e.target.closest("[data-action]");
413
413
  r && this.handleAction(r.dataset.action ?? "", r);
414
414
  });
@@ -447,80 +447,95 @@ class f extends HTMLElement {
447
447
  setError(e) {
448
448
  this.errorMsg = e, this.isLoading = !1, this.render();
449
449
  }
450
+ // ── Signal helpers ────────────────────────────────────────────────────────
451
+ getSignal(e = 15e3) {
452
+ var r;
453
+ const t = AbortSignal.timeout(e);
454
+ return (r = this.abortController) != null && r.signal ? AbortSignal.any([this.abortController.signal, t]) : t;
455
+ }
450
456
  // ── API calls ─────────────────────────────────────────────────────────────
451
457
  async loadSlots() {
452
- var e;
453
458
  this.setLoading(!0);
454
459
  try {
455
- const t = await fetch(`${this.baseApiPath}/booking-slots`, { signal: (e = this.abortController) == null ? void 0 : e.signal });
456
- if (!t.ok) throw new Error(`HTTP ${t.status}`);
457
- const r = await t.json();
458
- this.slots = r.slots, this.errorMsg = "";
459
- } catch {
460
- this.setError("Failed to load booking slots. Please try again.");
460
+ const e = await fetch(`${this.baseApiPath}/booking-slots`, { signal: this.getSignal() });
461
+ if (!e.ok) throw new Error(`HTTP ${e.status}`);
462
+ const t = await e.json();
463
+ this.slots = t.slots, this.errorMsg = "";
464
+ } catch (e) {
465
+ const t = e instanceof Error ? e.message : "Failed to load booking slots. Please try again.";
466
+ this.dispatchEvent(new CustomEvent("lsv-booking-error", {
467
+ bubbles: !0,
468
+ composed: !0,
469
+ detail: { message: t, step: "slots" }
470
+ })), this.setError("Failed to load booking slots. Please try again.");
461
471
  return;
462
472
  }
463
473
  this.isLoading = !1, this.render();
464
474
  }
465
475
  async loadTimes(e, t) {
466
- var r;
467
476
  this.setLoading(!0);
468
477
  try {
469
- const s = await fetch(
478
+ const r = await fetch(
470
479
  `${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,
471
- { signal: (r = this.abortController) == null ? void 0 : r.signal }
480
+ { signal: this.getSignal() }
472
481
  );
473
- if (!s.ok) throw new Error(`HTTP ${s.status}`);
474
- const o = await s.json();
475
- this.availableTimes = o.times, this.errorMsg = "";
476
- } catch {
477
- this.setError("Failed to load available times. Please try again.");
482
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
483
+ const s = await r.json();
484
+ s.times.length > 0 && typeof s.times[0] == "object" ? this.availableTimes = s.times.map((o) => o.startAt) : this.availableTimes = s.times, this.errorMsg = "";
485
+ } catch (r) {
486
+ const s = r instanceof Error ? r.message : "Failed to load available times. Please try again.";
487
+ this.dispatchEvent(new CustomEvent("lsv-booking-error", {
488
+ bubbles: !0,
489
+ composed: !0,
490
+ detail: { message: s, step: "time" }
491
+ })), this.setError("Failed to load available times. Please try again.");
478
492
  return;
479
493
  }
480
494
  this.isLoading = !1, this.step = "time", this.render();
481
495
  }
482
496
  async submitBooking(e, t, r, s) {
483
- var c;
484
497
  if (!this.selectedSlot || !this.selectedDate || !this.selectedTime) return;
485
498
  this.setLoading(!0);
486
- const o = (/* @__PURE__ */ new Date(`${this.selectedDate}T${this.selectedTime}`)).toISOString(), a = this.selectedSlot.id, n = {
499
+ const o = this.selectedTime.includes("T") ? this.selectedTime : (/* @__PURE__ */ new Date(`${this.selectedDate}T${this.selectedTime}`)).toISOString(), a = this.selectedSlot.id, i = {
487
500
  guestName: e,
488
501
  guestEmail: t,
489
502
  startAt: o
490
503
  };
491
- r && (n.guestPhone = r), s && (n.notes = s);
504
+ r && (i.guestPhone = r), s && (i.notes = s);
492
505
  try {
493
- const i = await fetch(
506
+ const c = await fetch(
494
507
  `${this.baseApiPath}/booking-slots/${a}/book`,
495
508
  {
496
509
  method: "POST",
497
510
  headers: { "Content-Type": "application/json" },
498
- body: JSON.stringify(n),
499
- signal: (c = this.abortController) == null ? void 0 : c.signal
511
+ body: JSON.stringify(i),
512
+ signal: this.getSignal()
500
513
  }
501
514
  );
502
- if (!i.ok) {
503
- const u = await i.json().catch(() => ({}));
504
- throw new Error(u.error ?? u.message ?? `HTTP ${i.status}`);
515
+ if (!c.ok) {
516
+ const d = await c.json().catch(() => ({}));
517
+ throw new Error(d.error ?? d.message ?? `HTTP ${c.status}`);
505
518
  }
506
- const l = await i.json();
507
- this.isLoading = !1, this.step = "success", this.render(), this.dispatchEvent(new CustomEvent("lsv-booking-submitted", {
519
+ const n = await c.json();
520
+ this.isLoading = !1, this.step = "success", this.render();
521
+ const l = new Date(n.startAt), p = new Date(l.getTime() + (this.selectedSlot.durationMin ?? 0) * 60 * 1e3);
522
+ this.dispatchEvent(new CustomEvent("lsv-booking-submitted", {
508
523
  bubbles: !0,
509
524
  composed: !0,
510
525
  detail: {
511
526
  bookingId: "",
512
- startAt: l.startAt,
513
- endAt: "",
527
+ startAt: n.startAt,
528
+ endAt: p.toISOString(),
514
529
  slotTitle: this.selectedSlot.title
515
530
  }
516
531
  }));
517
- } catch (i) {
518
- const l = i instanceof Error ? i.message : "Booking failed. Please try again.";
532
+ } catch (c) {
533
+ const n = c instanceof Error ? c.message : "Booking failed. Please try again.";
519
534
  this.dispatchEvent(new CustomEvent("lsv-booking-error", {
520
535
  bubbles: !0,
521
536
  composed: !0,
522
- detail: { message: l, step: "form" }
523
- })), this.setError(l);
537
+ detail: { message: n, step: "form" }
538
+ })), this.setError(n);
524
539
  }
525
540
  }
526
541
  // ── Date helpers ──────────────────────────────────────────────────────────
@@ -562,7 +577,7 @@ class f extends HTMLElement {
562
577
  // ── Render ────────────────────────────────────────────────────────────────
563
578
  render() {
564
579
  const e = document.createElement("style");
565
- e.textContent = k;
580
+ e.textContent = C;
566
581
  const t = document.createElement("div");
567
582
  if (t.className = "widget", this.step === "success")
568
583
  t.appendChild(this.renderSuccess());
@@ -589,14 +604,14 @@ class f extends HTMLElement {
589
604
  }
590
605
  renderStepIndicator() {
591
606
  const e = ["slots", "date", "time", "form"], t = e.indexOf(this.step), r = document.createElement("div");
592
- return r.className = "step-indicator", e.forEach((s, o) => {
593
- const a = document.createElement("div");
594
- a.className = `step-dot${o === t ? " active" : o < t ? " done" : ""}`, r.appendChild(a);
607
+ return r.className = "step-indicator", r.setAttribute("aria-label", "Booking progress"), r.setAttribute("role", "navigation"), e.forEach((s, o) => {
608
+ const a = o === t, i = document.createElement("div");
609
+ i.className = `step-dot${a ? " active" : o < t ? " done" : ""}`, i.setAttribute("aria-label", `Step ${o + 1}${a ? " (current)" : ""}`), a && i.setAttribute("aria-current", "step"), r.appendChild(i);
595
610
  }), r;
596
611
  }
597
612
  renderLoading() {
598
613
  const e = document.createElement("div");
599
- e.className = "loading";
614
+ e.className = "loading", e.setAttribute("aria-live", "polite"), e.setAttribute("role", "status");
600
615
  const t = document.createElement("div");
601
616
  t.className = "spinner";
602
617
  const r = document.createElement("span");
@@ -604,7 +619,7 @@ class f extends HTMLElement {
604
619
  }
605
620
  renderError() {
606
621
  const e = document.createDocumentFragment(), t = document.createElement("div");
607
- t.className = "error-box", t.textContent = this.errorMsg, e.appendChild(t);
622
+ t.className = "error-box", t.setAttribute("aria-live", "assertive"), t.setAttribute("role", "alert"), t.textContent = this.errorMsg, e.appendChild(t);
608
623
  const r = document.createElement("button");
609
624
  r.className = "btn-retry", r.dataset.action = "retry", r.textContent = "Try again", e.appendChild(r);
610
625
  const s = document.createElement("div");
@@ -620,12 +635,12 @@ class f extends HTMLElement {
620
635
  s.className = "slot-list", this.slots.forEach((o) => {
621
636
  const a = document.createElement("button");
622
637
  a.className = "slot-card", a.dataset.action = "select-slot", a.dataset.slotId = o.id;
623
- const n = document.createElement("p");
624
- n.className = "slot-card-title", n.textContent = o.title;
638
+ const i = document.createElement("p");
639
+ i.className = "slot-card-title", i.textContent = o.title;
625
640
  const c = document.createElement("p");
626
641
  c.className = "slot-card-meta";
627
- const i = this.formatDayList(o.daysOfWeek);
628
- if (c.textContent = `${o.durationMin} min · ${i} · ${o.startTime}–${o.endTime}`, a.appendChild(n), o.description) {
642
+ const n = this.formatDayList(o.daysOfWeek);
643
+ if (c.textContent = `${o.durationMin} min · ${n} · ${o.startTime}–${o.endTime}`, a.appendChild(i), o.description) {
629
644
  const l = document.createElement("p");
630
645
  l.className = "slot-card-meta", l.style.marginTop = "0.25rem", l.textContent = o.description, a.appendChild(l);
631
646
  }
@@ -653,29 +668,29 @@ class f extends HTMLElement {
653
668
  renderDate() {
654
669
  var c;
655
670
  const e = document.createDocumentFragment(), t = document.createElement("button");
656
- t.className = "btn-back", t.dataset.action = "back-to-slots", t.textContent = "← Back", e.appendChild(t);
671
+ t.className = "btn-back", t.dataset.action = "back-to-slots", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
657
672
  const r = document.createElement("p");
658
673
  r.className = "section-title", r.textContent = "Select a date", e.appendChild(r);
659
674
  const s = this.getNextDates(), o = document.createElement("div");
660
- o.className = "date-grid", x.forEach((i) => {
675
+ o.className = "date-grid", E.forEach((n) => {
661
676
  const l = document.createElement("div");
662
- l.className = "date-header", l.textContent = i, o.appendChild(l);
677
+ l.className = "date-header", l.textContent = n, o.appendChild(l);
663
678
  });
664
679
  const a = ((c = s[0]) == null ? void 0 : c.dayOfWeek) ?? 0;
665
- for (let i = 0; i < a; i++) {
680
+ for (let n = 0; n < a; n++) {
666
681
  const l = document.createElement("div");
667
682
  l.className = "date-cell empty", o.appendChild(l);
668
683
  }
669
- s.forEach(({ date: i, label: l, dayOfWeek: u }) => {
670
- const m = document.createElement("button"), g = this.isDateAllowed(u);
671
- m.className = `date-cell${g ? "" : " disabled"}${i === this.selectedDate ? " selected" : ""}`, m.textContent = (/* @__PURE__ */ new Date(`${i}T12:00:00`)).getDate().toString(), m.title = l, g ? (m.dataset.action = "select-date", m.dataset.date = i) : m.disabled = !0, o.appendChild(m);
684
+ s.forEach(({ date: n, label: l, dayOfWeek: p }) => {
685
+ const d = document.createElement("button"), b = this.isDateAllowed(p);
686
+ d.className = `date-cell${b ? "" : " disabled"}${n === this.selectedDate ? " selected" : ""}`, d.textContent = (/* @__PURE__ */ new Date(`${n}T12:00:00`)).getDate().toString(), d.title = l, d.setAttribute("aria-label", `Select ${l}`), b ? (d.dataset.action = "select-date", d.dataset.date = n) : d.disabled = !0, o.appendChild(d);
672
687
  }), e.appendChild(o);
673
- const n = document.createElement("div");
674
- return n.appendChild(e), n;
688
+ const i = document.createElement("div");
689
+ return i.appendChild(e), i;
675
690
  }
676
691
  renderTime() {
677
692
  const e = document.createDocumentFragment(), t = document.createElement("button");
678
- t.className = "btn-back", t.dataset.action = "back-to-date", t.textContent = "← Back", e.appendChild(t);
693
+ t.className = "btn-back", t.dataset.action = "back-to-date", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
679
694
  const r = document.createElement("p");
680
695
  if (r.className = "section-title", r.textContent = `Available times · ${this.formatDate(this.selectedDate)}`, e.appendChild(r), this.availableTimes.length === 0) {
681
696
  const o = document.createElement("p");
@@ -683,8 +698,8 @@ class f extends HTMLElement {
683
698
  } else {
684
699
  const o = document.createElement("div");
685
700
  o.className = "time-grid", this.availableTimes.forEach((a) => {
686
- const n = document.createElement("button");
687
- n.className = `time-btn${a === this.selectedTime ? " selected" : ""}`, n.dataset.action = "select-time", n.dataset.time = a, n.textContent = this.formatTime(a), o.appendChild(n);
701
+ const i = document.createElement("button");
702
+ i.className = `time-btn${a === this.selectedTime ? " selected" : ""}`, i.dataset.action = "select-time", i.dataset.time = a, i.textContent = this.formatTime(a), a === this.selectedTime && i.setAttribute("aria-pressed", "true"), o.appendChild(i);
688
703
  }), e.appendChild(o);
689
704
  }
690
705
  if (this.selectedTime) {
@@ -697,37 +712,37 @@ class f extends HTMLElement {
697
712
  return s.appendChild(e), s;
698
713
  }
699
714
  renderForm() {
700
- var g, b;
715
+ var b, g;
701
716
  const e = document.createDocumentFragment(), t = document.createElement("button");
702
- t.className = "btn-back", t.dataset.action = "back-to-time", t.textContent = "← Back", e.appendChild(t);
717
+ t.className = "btn-back", t.dataset.action = "back-to-time", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
703
718
  const r = document.createElement("div");
704
719
  r.className = "booking-summary";
705
720
  const s = document.createElement("strong");
706
- s.textContent = ((g = this.selectedSlot) == null ? void 0 : g.title) ?? "", r.appendChild(s);
721
+ s.textContent = ((b = this.selectedSlot) == null ? void 0 : b.title) ?? "", r.appendChild(s);
707
722
  const o = document.createElement("span");
708
723
  o.textContent = `${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`, r.appendChild(o), e.appendChild(r);
709
724
  const a = document.createElement("form");
710
- a.id = "lsv-booking-form", a.appendChild(this.makeField("guestName", "Full name", "text", !0)), a.appendChild(this.makeField("guestEmail", "Email address", "email", !0)), (b = this.selectedSlot) != null && b.requirePhone && a.appendChild(this.makeField("guestPhone", "Phone number", "tel", !0));
711
- const n = document.createElement("div");
712
- n.className = "form-group";
725
+ a.id = "lsv-booking-form", a.appendChild(this.makeField("guestName", "Full name", "text", !0)), a.appendChild(this.makeField("guestEmail", "Email address", "email", !0)), (g = this.selectedSlot) != null && g.requirePhone && a.appendChild(this.makeField("guestPhone", "Phone number", "tel", !0));
726
+ const i = document.createElement("div");
727
+ i.className = "form-group";
713
728
  const c = document.createElement("label");
714
729
  c.className = "form-label", c.setAttribute("for", "lsv-notes"), c.textContent = "Notes (optional)";
715
- const i = document.createElement("textarea");
716
- i.className = "form-textarea", i.id = "lsv-notes", i.name = "notes", i.rows = 3, i.placeholder = "Anything you'd like to share…", n.appendChild(c), n.appendChild(i), a.appendChild(n);
730
+ const n = document.createElement("textarea");
731
+ n.className = "form-textarea", n.id = "lsv-notes", n.name = "notes", n.rows = 3, n.placeholder = "Anything you'd like to share…", i.appendChild(c), i.appendChild(n), a.appendChild(i);
717
732
  const l = document.createElement("div");
718
733
  l.className = "actions";
719
- const u = document.createElement("button");
720
- u.type = "submit", u.className = "btn-primary", u.textContent = "Confirm Booking", l.appendChild(u), a.appendChild(l), e.appendChild(a);
721
- const m = document.createElement("div");
722
- return m.appendChild(e), m;
734
+ const p = document.createElement("button");
735
+ p.type = "submit", p.className = "btn-primary", p.textContent = "Confirm Booking", l.appendChild(p), a.appendChild(l), e.appendChild(a);
736
+ const d = document.createElement("div");
737
+ return d.appendChild(e), d;
723
738
  }
724
739
  makeField(e, t, r, s) {
725
740
  const o = document.createElement("div");
726
741
  o.className = "form-group";
727
742
  const a = document.createElement("label");
728
743
  a.className = "form-label", a.setAttribute("for", `lsv-${e}`), a.textContent = s ? `${t} *` : t;
729
- const n = document.createElement("input");
730
- return n.className = "form-input", n.type = r, n.id = `lsv-${e}`, n.name = e, n.required = s, n.autocomplete = e === "guestName" ? "name" : e === "guestEmail" ? "email" : e === "guestPhone" ? "tel" : "off", o.appendChild(a), o.appendChild(n), o;
744
+ const i = document.createElement("input");
745
+ return i.className = "form-input", i.type = r, i.id = `lsv-${e}`, i.name = e, i.required = s, i.autocomplete = e === "guestName" ? "name" : e === "guestEmail" ? "email" : e === "guestPhone" ? "tel" : "off", o.appendChild(a), o.appendChild(i), o;
731
746
  }
732
747
  renderSuccess() {
733
748
  const e = document.createElement("div");
@@ -743,8 +758,8 @@ class f extends HTMLElement {
743
758
  const e = this.root.querySelector("#lsv-booking-form");
744
759
  e && e.addEventListener("submit", (t) => {
745
760
  t.preventDefault();
746
- const r = new FormData(e), s = (r.get("guestName") ?? "").trim(), o = (r.get("guestEmail") ?? "").trim(), a = (r.get("guestPhone") ?? "").trim(), n = (r.get("notes") ?? "").trim();
747
- !s || !o || this.submitBooking(s, o, a, n);
761
+ const r = new FormData(e), s = (r.get("guestName") ?? "").trim(), o = (r.get("guestEmail") ?? "").trim(), a = (r.get("guestPhone") ?? "").trim(), i = (r.get("notes") ?? "").trim();
762
+ !s || !o || this.submitBooking(s, o, a, i);
748
763
  });
749
764
  }
750
765
  handleAction(e, t) {
@@ -785,7 +800,7 @@ class f extends HTMLElement {
785
800
  }
786
801
  }
787
802
  }
788
- d(f, "observedAttributes", ["api-url", "profile-slug", "vault-slug", "theme"]);
803
+ m(f, "observedAttributes", ["api-url", "profile-slug", "vault-slug", "theme"]);
789
804
  customElements.get("lsv-booking") || customElements.define("lsv-booking", f);
790
805
  export {
791
806
  f as LsvBooking
@@ -1,4 +1,4 @@
1
- (function(u,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(u=typeof globalThis<"u"?globalThis:u||self,d(u.LsvBooking={}))})(this,(function(u){"use strict";var y=Object.defineProperty;var k=(u,d,g)=>d in u?y(u,d,{enumerable:!0,configurable:!0,writable:!0,value:g}):u[d]=g;var m=(u,d,g)=>k(u,typeof d!="symbol"?d+"":d,g);const d=`
1
+ (function(p,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(p=typeof globalThis<"u"?globalThis:p||self,m(p.LsvBooking={}))})(this,(function(p){"use strict";var y=Object.defineProperty;var C=(p,m,b)=>m in p?y(p,m,{enumerable:!0,configurable:!0,writable:!0,value:b}):p[m]=b;var u=(p,m,b)=>C(p,typeof m!="symbol"?m+"":m,b);const m=`
2
2
  :host {
3
3
  display: block;
4
4
  font-family: system-ui, -apple-system, sans-serif;
@@ -390,4 +390,4 @@
390
390
  gap: 0.5rem;
391
391
  margin-top: 1rem;
392
392
  }
393
- `,g=["sun","mon","tue","wed","thu","fri","sat"],C=["Su","Mo","Tu","We","Th","Fr","Sa"];class f extends HTMLElement{constructor(){super();m(this,"root");m(this,"abortController",null);m(this,"step","slots");m(this,"slots",[]);m(this,"selectedSlot",null);m(this,"selectedDate","");m(this,"selectedTime","");m(this,"availableTimes",[]);m(this,"isLoading",!1);m(this,"errorMsg","");m(this,"handleRootClick",e=>{const r=e.target.closest("[data-action]");r&&this.handleAction(r.dataset.action??"",r)});this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.abortController=new AbortController,this.root.addEventListener("click",this.handleRootClick,{signal:this.abortController.signal}),this.render(),this.loadSlots()}disconnectedCallback(){var e;(e=this.abortController)==null||e.abort(),this.abortController=null}attributeChangedCallback(e,t,r){t!==r&&(e==="theme"?this.render():this.isConnected&&this.reset())}get apiUrl(){return(this.getAttribute("api-url")??"").replace(/\/$/,"")}get profileSlug(){return this.getAttribute("profile-slug")??""}get vaultSlug(){return this.getAttribute("vault-slug")??""}get baseApiPath(){return`${this.apiUrl}/api/v1/public/vaults/${this.profileSlug}/${this.vaultSlug}`}reset(){this.step="slots",this.slots=[],this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.availableTimes=[],this.isLoading=!1,this.errorMsg="",this.render(),this.loadSlots()}setLoading(e){this.isLoading=e,this.render()}setError(e){this.errorMsg=e,this.isLoading=!1,this.render()}async loadSlots(){var e;this.setLoading(!0);try{const t=await fetch(`${this.baseApiPath}/booking-slots`,{signal:(e=this.abortController)==null?void 0:e.signal});if(!t.ok)throw new Error(`HTTP ${t.status}`);const r=await t.json();this.slots=r.slots,this.errorMsg=""}catch{this.setError("Failed to load booking slots. Please try again.");return}this.isLoading=!1,this.render()}async loadTimes(e,t){var r;this.setLoading(!0);try{const s=await fetch(`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,{signal:(r=this.abortController)==null?void 0:r.signal});if(!s.ok)throw new Error(`HTTP ${s.status}`);const o=await s.json();this.availableTimes=o.times,this.errorMsg=""}catch{this.setError("Failed to load available times. Please try again.");return}this.isLoading=!1,this.step="time",this.render()}async submitBooking(e,t,r,s){var c;if(!this.selectedSlot||!this.selectedDate||!this.selectedTime)return;this.setLoading(!0);const o=new Date(`${this.selectedDate}T${this.selectedTime}`).toISOString(),a=this.selectedSlot.id,n={guestName:e,guestEmail:t,startAt:o};r&&(n.guestPhone=r),s&&(n.notes=s);try{const i=await fetch(`${this.baseApiPath}/booking-slots/${a}/book`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),signal:(c=this.abortController)==null?void 0:c.signal});if(!i.ok){const h=await i.json().catch(()=>({}));throw new Error(h.error??h.message??`HTTP ${i.status}`)}const l=await i.json();this.isLoading=!1,this.step="success",this.render(),this.dispatchEvent(new CustomEvent("lsv-booking-submitted",{bubbles:!0,composed:!0,detail:{bookingId:"",startAt:l.startAt,endAt:"",slotTitle:this.selectedSlot.title}}))}catch(i){const l=i instanceof Error?i.message:"Booking failed. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:l,step:"form"}})),this.setError(l)}}getNextDates(){const e=[],t=new Date;t.setHours(0,0,0,0);for(let r=0;r<14;r++){const s=new Date(t.getTime()+r*864e5),o=s.toISOString().split("T")[0],a=s.toLocaleDateString("en-US",{month:"short",day:"numeric"});e.push({date:o,label:a,dayOfWeek:s.getDay()})}return e}isDateAllowed(e){if(!this.selectedSlot)return!1;const t=g[e];return this.selectedSlot.daysOfWeek.includes(t)}formatTime(e){try{if(e.includes("T"))return new Date(e).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0});const[t,r]=e.split(":").map(Number),s=new Date;return s.setHours(t,r,0,0),s.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}catch{return e}}formatDate(e){try{return new Date(`${e}T12:00:00`).toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"})}catch{return e}}render(){const e=document.createElement("style");e.textContent=d;const t=document.createElement("div");if(t.className="widget",this.step==="success")t.appendChild(this.renderSuccess());else if(t.appendChild(this.renderStepIndicator()),this.isLoading)t.appendChild(this.renderLoading());else if(this.errorMsg)t.appendChild(this.renderError());else switch(this.step){case"slots":t.appendChild(this.renderSlots());break;case"date":t.appendChild(this.renderDate());break;case"time":t.appendChild(this.renderTime());break;case"form":t.appendChild(this.renderForm());break}this.root.replaceChildren(e,t),this.attachEvents()}renderStepIndicator(){const e=["slots","date","time","form"],t=e.indexOf(this.step),r=document.createElement("div");return r.className="step-indicator",e.forEach((s,o)=>{const a=document.createElement("div");a.className=`step-dot${o===t?" active":o<t?" done":""}`,r.appendChild(a)}),r}renderLoading(){const e=document.createElement("div");e.className="loading";const t=document.createElement("div");t.className="spinner";const r=document.createElement("span");return r.textContent="Loading…",e.appendChild(t),e.appendChild(r),e}renderError(){const e=document.createDocumentFragment(),t=document.createElement("div");t.className="error-box",t.textContent=this.errorMsg,e.appendChild(t);const r=document.createElement("button");r.className="btn-retry",r.dataset.action="retry",r.textContent="Try again",e.appendChild(r);const s=document.createElement("div");return s.appendChild(e),s}renderSlots(){const e=document.createDocumentFragment(),t=document.createElement("p");if(t.className="section-title",t.textContent="Choose a booking type",e.appendChild(t),this.slots.length===0){const s=document.createElement("p");s.className="empty-msg",s.textContent="No booking slots available.",e.appendChild(s)}else{const s=document.createElement("div");s.className="slot-list",this.slots.forEach(o=>{const a=document.createElement("button");a.className="slot-card",a.dataset.action="select-slot",a.dataset.slotId=o.id;const n=document.createElement("p");n.className="slot-card-title",n.textContent=o.title;const c=document.createElement("p");c.className="slot-card-meta";const i=this.formatDayList(o.daysOfWeek);if(c.textContent=`${o.durationMin} min · ${i} · ${o.startTime}–${o.endTime}`,a.appendChild(n),o.description){const l=document.createElement("p");l.className="slot-card-meta",l.style.marginTop="0.25rem",l.textContent=o.description,a.appendChild(l)}a.appendChild(c),s.appendChild(a)}),e.appendChild(s)}const r=document.createElement("div");return r.appendChild(e),r}formatDayList(e){if(e.length===7)return"Every day";if(e.length===5&&!e.includes("sat")&&!e.includes("sun"))return"Weekdays";if(e.length===2&&e.includes("sat")&&e.includes("sun"))return"Weekends";const t={mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat",sun:"Sun"};return e.map(r=>t[r]??r).join(", ")}renderDate(){var c;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-slots",t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");r.className="section-title",r.textContent="Select a date",e.appendChild(r);const s=this.getNextDates(),o=document.createElement("div");o.className="date-grid",C.forEach(i=>{const l=document.createElement("div");l.className="date-header",l.textContent=i,o.appendChild(l)});const a=((c=s[0])==null?void 0:c.dayOfWeek)??0;for(let i=0;i<a;i++){const l=document.createElement("div");l.className="date-cell empty",o.appendChild(l)}s.forEach(({date:i,label:l,dayOfWeek:h})=>{const p=document.createElement("button"),b=this.isDateAllowed(h);p.className=`date-cell${b?"":" disabled"}${i===this.selectedDate?" selected":""}`,p.textContent=new Date(`${i}T12:00:00`).getDate().toString(),p.title=l,b?(p.dataset.action="select-date",p.dataset.date=i):p.disabled=!0,o.appendChild(p)}),e.appendChild(o);const n=document.createElement("div");return n.appendChild(e),n}renderTime(){const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-date",t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");if(r.className="section-title",r.textContent=`Available times · ${this.formatDate(this.selectedDate)}`,e.appendChild(r),this.availableTimes.length===0){const o=document.createElement("p");o.className="empty-msg",o.textContent="No times available on this date. Try another day.",e.appendChild(o)}else{const o=document.createElement("div");o.className="time-grid",this.availableTimes.forEach(a=>{const n=document.createElement("button");n.className=`time-btn${a===this.selectedTime?" selected":""}`,n.dataset.action="select-time",n.dataset.time=a,n.textContent=this.formatTime(a),o.appendChild(n)}),e.appendChild(o)}if(this.selectedTime){const o=document.createElement("div");o.className="actions";const a=document.createElement("button");a.className="btn-primary",a.dataset.action="go-to-form",a.textContent="Continue",o.appendChild(a),e.appendChild(o)}const s=document.createElement("div");return s.appendChild(e),s}renderForm(){var b,v;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-time",t.textContent="← Back",e.appendChild(t);const r=document.createElement("div");r.className="booking-summary";const s=document.createElement("strong");s.textContent=((b=this.selectedSlot)==null?void 0:b.title)??"",r.appendChild(s);const o=document.createElement("span");o.textContent=`${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`,r.appendChild(o),e.appendChild(r);const a=document.createElement("form");a.id="lsv-booking-form",a.appendChild(this.makeField("guestName","Full name","text",!0)),a.appendChild(this.makeField("guestEmail","Email address","email",!0)),(v=this.selectedSlot)!=null&&v.requirePhone&&a.appendChild(this.makeField("guestPhone","Phone number","tel",!0));const n=document.createElement("div");n.className="form-group";const c=document.createElement("label");c.className="form-label",c.setAttribute("for","lsv-notes"),c.textContent="Notes (optional)";const i=document.createElement("textarea");i.className="form-textarea",i.id="lsv-notes",i.name="notes",i.rows=3,i.placeholder="Anything you'd like to share…",n.appendChild(c),n.appendChild(i),a.appendChild(n);const l=document.createElement("div");l.className="actions";const h=document.createElement("button");h.type="submit",h.className="btn-primary",h.textContent="Confirm Booking",l.appendChild(h),a.appendChild(l),e.appendChild(a);const p=document.createElement("div");return p.appendChild(e),p}makeField(e,t,r,s){const o=document.createElement("div");o.className="form-group";const a=document.createElement("label");a.className="form-label",a.setAttribute("for",`lsv-${e}`),a.textContent=s?`${t} *`:t;const n=document.createElement("input");return n.className="form-input",n.type=r,n.id=`lsv-${e}`,n.name=e,n.required=s,n.autocomplete=e==="guestName"?"name":e==="guestEmail"?"email":e==="guestPhone"?"tel":"off",o.appendChild(a),o.appendChild(n),o}renderSuccess(){const e=document.createElement("div");e.className="success-view";const t=document.createElement("div");t.className="success-icon",t.textContent="✓";const r=document.createElement("p");r.className="success-title",r.textContent="Booking requested!";const s=document.createElement("p");return s.className="success-msg",s.textContent="Check your email to confirm your booking.",e.appendChild(t),e.appendChild(r),e.appendChild(s),e}attachEvents(){const e=this.root.querySelector("#lsv-booking-form");e&&e.addEventListener("submit",t=>{t.preventDefault();const r=new FormData(e),s=(r.get("guestName")??"").trim(),o=(r.get("guestEmail")??"").trim(),a=(r.get("guestPhone")??"").trim(),n=(r.get("notes")??"").trim();!s||!o||this.submitBooking(s,o,a,n)})}handleAction(e,t){switch(e){case"retry":this.errorMsg="",this.step==="slots"?this.loadSlots():this.step==="time"&&this.selectedSlot?this.loadTimes(this.selectedSlot.id,this.selectedDate):this.render();break;case"select-slot":{const r=t.dataset.slotId,s=this.slots.find(o=>o.id===r);if(!s)return;this.selectedSlot=s,this.step="date",this.render();break}case"back-to-slots":this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.step="slots",this.render();break;case"select-date":{const r=t.dataset.date;if(!r||!this.selectedSlot)return;this.selectedDate=r,this.selectedTime="",this.loadTimes(this.selectedSlot.id,r);break}case"back-to-date":this.selectedTime="",this.step="date",this.render();break;case"select-time":{const r=t.dataset.time;if(!r)return;this.selectedTime=r,this.render();break}case"go-to-form":this.selectedTime&&(this.step="form",this.render());break;case"back-to-time":this.step="time",this.render();break}}}m(f,"observedAttributes",["api-url","profile-slug","vault-slug","theme"]),customElements.get("lsv-booking")||customElements.define("lsv-booking",f),u.LsvBooking=f,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
393
+ `,b=["sun","mon","tue","wed","thu","fri","sat"],k=["Su","Mo","Tu","We","Th","Fr","Sa"];class f extends HTMLElement{constructor(){super();u(this,"root");u(this,"abortController",null);u(this,"step","slots");u(this,"slots",[]);u(this,"selectedSlot",null);u(this,"selectedDate","");u(this,"selectedTime","");u(this,"availableTimes",[]);u(this,"isLoading",!1);u(this,"errorMsg","");u(this,"handleRootClick",e=>{const r=e.target.closest("[data-action]");r&&this.handleAction(r.dataset.action??"",r)});this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.abortController=new AbortController,this.root.addEventListener("click",this.handleRootClick,{signal:this.abortController.signal}),this.render(),this.loadSlots()}disconnectedCallback(){var e;(e=this.abortController)==null||e.abort(),this.abortController=null}attributeChangedCallback(e,t,r){t!==r&&(e==="theme"?this.render():this.isConnected&&this.reset())}get apiUrl(){return(this.getAttribute("api-url")??"").replace(/\/$/,"")}get profileSlug(){return this.getAttribute("profile-slug")??""}get vaultSlug(){return this.getAttribute("vault-slug")??""}get baseApiPath(){return`${this.apiUrl}/api/v1/public/vaults/${this.profileSlug}/${this.vaultSlug}`}reset(){this.step="slots",this.slots=[],this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.availableTimes=[],this.isLoading=!1,this.errorMsg="",this.render(),this.loadSlots()}setLoading(e){this.isLoading=e,this.render()}setError(e){this.errorMsg=e,this.isLoading=!1,this.render()}getSignal(e=15e3){var r;const t=AbortSignal.timeout(e);return(r=this.abortController)!=null&&r.signal?AbortSignal.any([this.abortController.signal,t]):t}async loadSlots(){this.setLoading(!0);try{const e=await fetch(`${this.baseApiPath}/booking-slots`,{signal:this.getSignal()});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=await e.json();this.slots=t.slots,this.errorMsg=""}catch(e){const t=e instanceof Error?e.message:"Failed to load booking slots. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:t,step:"slots"}})),this.setError("Failed to load booking slots. Please try again.");return}this.isLoading=!1,this.render()}async loadTimes(e,t){this.setLoading(!0);try{const r=await fetch(`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,{signal:this.getSignal()});if(!r.ok)throw new Error(`HTTP ${r.status}`);const o=await r.json();o.times.length>0&&typeof o.times[0]=="object"?this.availableTimes=o.times.map(s=>s.startAt):this.availableTimes=o.times,this.errorMsg=""}catch(r){const o=r instanceof Error?r.message:"Failed to load available times. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:o,step:"time"}})),this.setError("Failed to load available times. Please try again.");return}this.isLoading=!1,this.step="time",this.render()}async submitBooking(e,t,r,o){if(!this.selectedSlot||!this.selectedDate||!this.selectedTime)return;this.setLoading(!0);const s=this.selectedTime.includes("T")?this.selectedTime:new Date(`${this.selectedDate}T${this.selectedTime}`).toISOString(),a=this.selectedSlot.id,i={guestName:e,guestEmail:t,startAt:s};r&&(i.guestPhone=r),o&&(i.notes=o);try{const c=await fetch(`${this.baseApiPath}/booking-slots/${a}/book`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i),signal:this.getSignal()});if(!c.ok){const d=await c.json().catch(()=>({}));throw new Error(d.error??d.message??`HTTP ${c.status}`)}const n=await c.json();this.isLoading=!1,this.step="success",this.render();const l=new Date(n.startAt),h=new Date(l.getTime()+(this.selectedSlot.durationMin??0)*60*1e3);this.dispatchEvent(new CustomEvent("lsv-booking-submitted",{bubbles:!0,composed:!0,detail:{bookingId:"",startAt:n.startAt,endAt:h.toISOString(),slotTitle:this.selectedSlot.title}}))}catch(c){const n=c instanceof Error?c.message:"Booking failed. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:n,step:"form"}})),this.setError(n)}}getNextDates(){const e=[],t=new Date;t.setHours(0,0,0,0);for(let r=0;r<14;r++){const o=new Date(t.getTime()+r*864e5),s=o.toISOString().split("T")[0],a=o.toLocaleDateString("en-US",{month:"short",day:"numeric"});e.push({date:s,label:a,dayOfWeek:o.getDay()})}return e}isDateAllowed(e){if(!this.selectedSlot)return!1;const t=b[e];return this.selectedSlot.daysOfWeek.includes(t)}formatTime(e){try{if(e.includes("T"))return new Date(e).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0});const[t,r]=e.split(":").map(Number),o=new Date;return o.setHours(t,r,0,0),o.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}catch{return e}}formatDate(e){try{return new Date(`${e}T12:00:00`).toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"})}catch{return e}}render(){const e=document.createElement("style");e.textContent=m;const t=document.createElement("div");if(t.className="widget",this.step==="success")t.appendChild(this.renderSuccess());else if(t.appendChild(this.renderStepIndicator()),this.isLoading)t.appendChild(this.renderLoading());else if(this.errorMsg)t.appendChild(this.renderError());else switch(this.step){case"slots":t.appendChild(this.renderSlots());break;case"date":t.appendChild(this.renderDate());break;case"time":t.appendChild(this.renderTime());break;case"form":t.appendChild(this.renderForm());break}this.root.replaceChildren(e,t),this.attachEvents()}renderStepIndicator(){const e=["slots","date","time","form"],t=e.indexOf(this.step),r=document.createElement("div");return r.className="step-indicator",r.setAttribute("aria-label","Booking progress"),r.setAttribute("role","navigation"),e.forEach((o,s)=>{const a=s===t,i=document.createElement("div");i.className=`step-dot${a?" active":s<t?" done":""}`,i.setAttribute("aria-label",`Step ${s+1}${a?" (current)":""}`),a&&i.setAttribute("aria-current","step"),r.appendChild(i)}),r}renderLoading(){const e=document.createElement("div");e.className="loading",e.setAttribute("aria-live","polite"),e.setAttribute("role","status");const t=document.createElement("div");t.className="spinner";const r=document.createElement("span");return r.textContent="Loading…",e.appendChild(t),e.appendChild(r),e}renderError(){const e=document.createDocumentFragment(),t=document.createElement("div");t.className="error-box",t.setAttribute("aria-live","assertive"),t.setAttribute("role","alert"),t.textContent=this.errorMsg,e.appendChild(t);const r=document.createElement("button");r.className="btn-retry",r.dataset.action="retry",r.textContent="Try again",e.appendChild(r);const o=document.createElement("div");return o.appendChild(e),o}renderSlots(){const e=document.createDocumentFragment(),t=document.createElement("p");if(t.className="section-title",t.textContent="Choose a booking type",e.appendChild(t),this.slots.length===0){const o=document.createElement("p");o.className="empty-msg",o.textContent="No booking slots available.",e.appendChild(o)}else{const o=document.createElement("div");o.className="slot-list",this.slots.forEach(s=>{const a=document.createElement("button");a.className="slot-card",a.dataset.action="select-slot",a.dataset.slotId=s.id;const i=document.createElement("p");i.className="slot-card-title",i.textContent=s.title;const c=document.createElement("p");c.className="slot-card-meta";const n=this.formatDayList(s.daysOfWeek);if(c.textContent=`${s.durationMin} min · ${n} · ${s.startTime}–${s.endTime}`,a.appendChild(i),s.description){const l=document.createElement("p");l.className="slot-card-meta",l.style.marginTop="0.25rem",l.textContent=s.description,a.appendChild(l)}a.appendChild(c),o.appendChild(a)}),e.appendChild(o)}const r=document.createElement("div");return r.appendChild(e),r}formatDayList(e){if(e.length===7)return"Every day";if(e.length===5&&!e.includes("sat")&&!e.includes("sun"))return"Weekdays";if(e.length===2&&e.includes("sat")&&e.includes("sun"))return"Weekends";const t={mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat",sun:"Sun"};return e.map(r=>t[r]??r).join(", ")}renderDate(){var c;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-slots",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");r.className="section-title",r.textContent="Select a date",e.appendChild(r);const o=this.getNextDates(),s=document.createElement("div");s.className="date-grid",k.forEach(n=>{const l=document.createElement("div");l.className="date-header",l.textContent=n,s.appendChild(l)});const a=((c=o[0])==null?void 0:c.dayOfWeek)??0;for(let n=0;n<a;n++){const l=document.createElement("div");l.className="date-cell empty",s.appendChild(l)}o.forEach(({date:n,label:l,dayOfWeek:h})=>{const d=document.createElement("button"),g=this.isDateAllowed(h);d.className=`date-cell${g?"":" disabled"}${n===this.selectedDate?" selected":""}`,d.textContent=new Date(`${n}T12:00:00`).getDate().toString(),d.title=l,d.setAttribute("aria-label",`Select ${l}`),g?(d.dataset.action="select-date",d.dataset.date=n):d.disabled=!0,s.appendChild(d)}),e.appendChild(s);const i=document.createElement("div");return i.appendChild(e),i}renderTime(){const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-date",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");if(r.className="section-title",r.textContent=`Available times · ${this.formatDate(this.selectedDate)}`,e.appendChild(r),this.availableTimes.length===0){const s=document.createElement("p");s.className="empty-msg",s.textContent="No times available on this date. Try another day.",e.appendChild(s)}else{const s=document.createElement("div");s.className="time-grid",this.availableTimes.forEach(a=>{const i=document.createElement("button");i.className=`time-btn${a===this.selectedTime?" selected":""}`,i.dataset.action="select-time",i.dataset.time=a,i.textContent=this.formatTime(a),a===this.selectedTime&&i.setAttribute("aria-pressed","true"),s.appendChild(i)}),e.appendChild(s)}if(this.selectedTime){const s=document.createElement("div");s.className="actions";const a=document.createElement("button");a.className="btn-primary",a.dataset.action="go-to-form",a.textContent="Continue",s.appendChild(a),e.appendChild(s)}const o=document.createElement("div");return o.appendChild(e),o}renderForm(){var g,v;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-time",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("div");r.className="booking-summary";const o=document.createElement("strong");o.textContent=((g=this.selectedSlot)==null?void 0:g.title)??"",r.appendChild(o);const s=document.createElement("span");s.textContent=`${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`,r.appendChild(s),e.appendChild(r);const a=document.createElement("form");a.id="lsv-booking-form",a.appendChild(this.makeField("guestName","Full name","text",!0)),a.appendChild(this.makeField("guestEmail","Email address","email",!0)),(v=this.selectedSlot)!=null&&v.requirePhone&&a.appendChild(this.makeField("guestPhone","Phone number","tel",!0));const i=document.createElement("div");i.className="form-group";const c=document.createElement("label");c.className="form-label",c.setAttribute("for","lsv-notes"),c.textContent="Notes (optional)";const n=document.createElement("textarea");n.className="form-textarea",n.id="lsv-notes",n.name="notes",n.rows=3,n.placeholder="Anything you'd like to share…",i.appendChild(c),i.appendChild(n),a.appendChild(i);const l=document.createElement("div");l.className="actions";const h=document.createElement("button");h.type="submit",h.className="btn-primary",h.textContent="Confirm Booking",l.appendChild(h),a.appendChild(l),e.appendChild(a);const d=document.createElement("div");return d.appendChild(e),d}makeField(e,t,r,o){const s=document.createElement("div");s.className="form-group";const a=document.createElement("label");a.className="form-label",a.setAttribute("for",`lsv-${e}`),a.textContent=o?`${t} *`:t;const i=document.createElement("input");return i.className="form-input",i.type=r,i.id=`lsv-${e}`,i.name=e,i.required=o,i.autocomplete=e==="guestName"?"name":e==="guestEmail"?"email":e==="guestPhone"?"tel":"off",s.appendChild(a),s.appendChild(i),s}renderSuccess(){const e=document.createElement("div");e.className="success-view";const t=document.createElement("div");t.className="success-icon",t.textContent="✓";const r=document.createElement("p");r.className="success-title",r.textContent="Booking requested!";const o=document.createElement("p");return o.className="success-msg",o.textContent="Check your email to confirm your booking.",e.appendChild(t),e.appendChild(r),e.appendChild(o),e}attachEvents(){const e=this.root.querySelector("#lsv-booking-form");e&&e.addEventListener("submit",t=>{t.preventDefault();const r=new FormData(e),o=(r.get("guestName")??"").trim(),s=(r.get("guestEmail")??"").trim(),a=(r.get("guestPhone")??"").trim(),i=(r.get("notes")??"").trim();!o||!s||this.submitBooking(o,s,a,i)})}handleAction(e,t){switch(e){case"retry":this.errorMsg="",this.step==="slots"?this.loadSlots():this.step==="time"&&this.selectedSlot?this.loadTimes(this.selectedSlot.id,this.selectedDate):this.render();break;case"select-slot":{const r=t.dataset.slotId,o=this.slots.find(s=>s.id===r);if(!o)return;this.selectedSlot=o,this.step="date",this.render();break}case"back-to-slots":this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.step="slots",this.render();break;case"select-date":{const r=t.dataset.date;if(!r||!this.selectedSlot)return;this.selectedDate=r,this.selectedTime="",this.loadTimes(this.selectedSlot.id,r);break}case"back-to-date":this.selectedTime="",this.step="date",this.render();break;case"select-time":{const r=t.dataset.time;if(!r)return;this.selectedTime=r,this.render();break}case"go-to-form":this.selectedTime&&(this.step="form",this.render());break;case"back-to-time":this.step="time",this.render();break}}}u(f,"observedAttributes",["api-url","profile-slug","vault-slug","theme"]),customElements.get("lsv-booking")||customElements.define("lsv-booking",f),p.LsvBooking=f,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifestreamdynamics/booking-widget",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Embeddable booking widget Web Component for Lifestream Vault",
5
5
  "type": "module",
6
6
  "main": "dist/lsv-booking.umd.cjs",
@@ -48,6 +48,6 @@
48
48
  "typescript": "^5.7.3",
49
49
  "vite": "^6.1.0",
50
50
  "vite-plugin-dts": "^4.5.0",
51
- "vitest": "^3.0.5"
51
+ "vitest": "^4.0.18"
52
52
  }
53
53
  }