@forcecalendar/interface 1.0.59 → 1.0.60

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.
@@ -1,7 +1,7 @@
1
1
  var H = Object.defineProperty;
2
- var F = (h, t, e) => t in h ? H(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e;
3
- var v = (h, t, e) => F(h, typeof t != "symbol" ? t + "" : t, e);
4
- import { Calendar as L, DateUtils as I } from "@forcecalendar/core";
2
+ var L = (h, t, e) => t in h ? H(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e;
3
+ var m = (h, t, e) => L(h, typeof t != "symbol" ? t + "" : t, e);
4
+ import { Calendar as F, DateUtils as I } from "@forcecalendar/core";
5
5
  class M extends HTMLElement {
6
6
  constructor() {
7
7
  super(), this.attachShadow({ mode: "open" }), this._listeners = [], this._state = null, this._props = /* @__PURE__ */ new Map(), this._initialized = !1;
@@ -149,7 +149,7 @@ class M extends HTMLElement {
149
149
  this.setProp(t, r), this._initialized && this.render();
150
150
  }
151
151
  }
152
- class S {
152
+ class T {
153
153
  constructor() {
154
154
  this.events = /* @__PURE__ */ new Map(), this.wildcardHandlers = /* @__PURE__ */ new Set();
155
155
  }
@@ -289,10 +289,10 @@ class S {
289
289
  return t;
290
290
  }
291
291
  }
292
- const U = new S();
292
+ const U = new T();
293
293
  class V {
294
294
  constructor(t = {}) {
295
- this.eventBus = new S(), this.calendar = new L({
295
+ this.eventBus = new T(), this.calendar = new F({
296
296
  view: t.view || "month",
297
297
  date: t.date || /* @__PURE__ */ new Date(),
298
298
  weekStartsOn: t.weekStartsOn ?? 0,
@@ -647,7 +647,7 @@ class u extends I {
647
647
  return s && (s.toLowerCase() === "pm" && n < 12 ? o = n + 12 : s.toLowerCase() === "am" && n === 12 && (o = 0)), r.setHours(o, a || 0, 0, 0), r;
648
648
  }
649
649
  }
650
- class y {
650
+ class g {
651
651
  /**
652
652
  * Create element with attributes and children
653
653
  */
@@ -739,13 +739,19 @@ class y {
739
739
  }
740
740
  /**
741
741
  * Wait for animation/transition to complete
742
- */
743
- static waitForAnimation(t, e = "animationend") {
744
- return new Promise((r) => {
745
- const i = () => {
746
- t.removeEventListener(e, i), r();
742
+ * Resolves after `timeout` ms even if the event never fires (animation
743
+ * cancelled, element removed from DOM, etc.) so awaiting callers can't hang.
744
+ * @param {Element} element - Element to listen on
745
+ * @param {string} [eventType='animationend'] - Event to wait for
746
+ * @param {number} [timeout=3000] - Max wait in ms; 0 disables the timeout
747
+ */
748
+ static waitForAnimation(t, e = "animationend", r = 3e3) {
749
+ return new Promise((i) => {
750
+ let s = null;
751
+ const n = () => {
752
+ s !== null && clearTimeout(s), t.removeEventListener(e, n), i();
747
753
  };
748
- t.addEventListener(e, i);
754
+ t.addEventListener(e, n), r > 0 && (s = setTimeout(n, r));
749
755
  });
750
756
  }
751
757
  /**
@@ -756,10 +762,30 @@ class y {
756
762
  }
757
763
  /**
758
764
  * Parse HTML string safely
759
- */
760
- static parseHTML(t) {
761
- const e = document.createElement("template");
762
- return e.innerHTML = t.trim(), e.content.firstChild;
765
+ * Sanitizes by default: strips script-capable elements, inline event
766
+ * handlers and javascript: URLs so the returned node is inert even if the
767
+ * input contains an XSS payload. Pass { sanitize: false } only for trusted,
768
+ * non-user-controlled markup.
769
+ */
770
+ static parseHTML(t, { sanitize: e = !0 } = {}) {
771
+ const r = document.createElement("template");
772
+ return r.innerHTML = t.trim(), e && this._sanitizeNode(r.content), r.content.firstChild;
773
+ }
774
+ /**
775
+ * Remove script-capable elements, on* handlers and javascript: URLs
776
+ * from a parsed DOM fragment (in place)
777
+ */
778
+ static _sanitizeNode(t) {
779
+ t.querySelectorAll("script, iframe, object, embed, link, meta, base").forEach((s) => s.remove());
780
+ const r = document.createTreeWalker(t, NodeFilter.SHOW_ELEMENT);
781
+ let i = r.nextNode();
782
+ for (; i; ) {
783
+ for (const s of Array.from(i.attributes)) {
784
+ const n = s.name.toLowerCase(), a = s.value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
785
+ (n.startsWith("on") || a.startsWith("javascript:") || a.startsWith("data:text/html")) && i.removeAttribute(s.name);
786
+ }
787
+ i = r.nextNode();
788
+ }
763
789
  }
764
790
  /**
765
791
  * Escape HTML to prevent XSS
@@ -819,7 +845,10 @@ class y {
819
845
  };
820
846
  }
821
847
  /**
822
- * Clone element with event listeners
848
+ * Clone an element. Event listeners are NOT copied — the Web Platform
849
+ * provides no way to enumerate listeners, so callers must re-attach their
850
+ * own handlers to the clone.
851
+ * @deprecated Use element.cloneNode(deep) directly and re-bind listeners.
823
852
  */
824
853
  static cloneWithEvents(t, e = !0) {
825
854
  return t.cloneNode(e);
@@ -834,7 +863,9 @@ class y {
834
863
  if (e.length === 0)
835
864
  return t.setAttribute("tabindex", "-1"), t.focus(), () => t.removeAttribute("tabindex");
836
865
  const r = e[0], i = e[e.length - 1], s = (n) => {
837
- n.key === "Tab" && (n.shiftKey ? document.activeElement === r && (i == null || i.focus(), n.preventDefault()) : document.activeElement === i && (r == null || r.focus(), n.preventDefault()));
866
+ if (n.key !== "Tab") return;
867
+ const o = t.getRootNode().activeElement ?? document.activeElement;
868
+ n.shiftKey ? o === r && (i == null || i.focus(), n.preventDefault()) : o === i && (r == null || r.focus(), n.preventDefault());
838
869
  };
839
870
  return t.addEventListener("keydown", s), r == null || r.focus(), () => t.removeEventListener("keydown", s);
840
871
  }
@@ -1021,7 +1052,7 @@ class f {
1021
1052
  * Get contrast color (black or white) for background
1022
1053
  */
1023
1054
  static getContrastColor(t) {
1024
- const e = t.replace("#", ""), r = parseInt(e.substr(0, 2), 16), i = parseInt(e.substr(2, 2), 16), s = parseInt(e.substr(4, 2), 16);
1055
+ const e = t.replace("#", ""), r = parseInt(e.substring(0, 2), 16), i = parseInt(e.substring(2, 4), 16), s = parseInt(e.substring(4, 6), 16);
1025
1056
  return (r * 299 + i * 587 + s * 114) / 1e3 >= 128 ? "#000000" : "#FFFFFF";
1026
1057
  }
1027
1058
  /**
@@ -1064,7 +1095,7 @@ class f {
1064
1095
  * Convert hex to rgba
1065
1096
  */
1066
1097
  static hexToRgba(t, e = 1) {
1067
- const r = t.replace("#", ""), i = parseInt(r.substr(0, 2), 16), s = parseInt(r.substr(2, 2), 16), n = parseInt(r.substr(4, 2), 16);
1098
+ const r = t.replace("#", ""), i = parseInt(r.substring(0, 2), 16), s = parseInt(r.substring(2, 4), 16), n = parseInt(r.substring(4, 6), 16);
1068
1099
  return `rgba(${i}, ${s}, ${n}, ${e})`;
1069
1100
  }
1070
1101
  /**
@@ -1176,7 +1207,7 @@ class f {
1176
1207
  /**
1177
1208
  * Default theme colors
1178
1209
  */
1179
- v(f, "colors", {
1210
+ m(f, "colors", {
1180
1211
  primary: "#3B82F6",
1181
1212
  // Modern Blue
1182
1213
  secondary: "#64748B",
@@ -1209,7 +1240,7 @@ v(f, "colors", {
1209
1240
  }), /**
1210
1241
  * Common CSS variables
1211
1242
  */
1212
- v(f, "cssVariables", {
1243
+ m(f, "cssVariables", {
1213
1244
  // "Pro" Palette - Functional & Sharp
1214
1245
  "--fc-primary-color": "#2563EB",
1215
1246
  // International Blue (Focus)
@@ -1277,7 +1308,7 @@ v(f, "cssVariables", {
1277
1308
  }), /**
1278
1309
  * Get responsive breakpoints
1279
1310
  */
1280
- v(f, "breakpoints", {
1311
+ m(f, "breakpoints", {
1281
1312
  xs: "320px",
1282
1313
  sm: "576px",
1283
1314
  md: "768px",
@@ -1285,7 +1316,7 @@ v(f, "breakpoints", {
1285
1316
  xl: "1200px",
1286
1317
  "2xl": "1400px"
1287
1318
  });
1288
- class k {
1319
+ class D {
1289
1320
  /**
1290
1321
  * @param {HTMLElement} container - The DOM element to render into
1291
1322
  * @param {StateManager} stateManager - The state manager instance
@@ -1324,7 +1355,7 @@ class k {
1324
1355
  * @returns {string}
1325
1356
  */
1326
1357
  escapeHTML(t) {
1327
- return t == null ? "" : y.escapeHTML(String(t));
1358
+ return t == null ? "" : g.escapeHTML(String(t));
1328
1359
  }
1329
1360
  /**
1330
1361
  * Check if a date is today
@@ -1439,18 +1470,18 @@ class k {
1439
1470
  * @returns {string} HTML string
1440
1471
  */
1441
1472
  renderTimedEvent(t, e = {}) {
1442
- const { compact: r = !0, overlapLayout: i = null } = e, s = new Date(t.start), n = new Date(t.end), a = s.getHours() * 60 + s.getMinutes(), o = Math.max((n - s) / (1e3 * 60), r ? 20 : 30), c = this.getEventColor(t), l = this.getContrastingTextColor(c), d = r ? "4px 8px" : "8px 12px", p = r ? "11px" : "13px", g = r ? 2 : 12, b = r ? 2 : 24, T = r ? "4px" : "6px";
1473
+ const { compact: r = !0, overlapLayout: i = null } = e, s = new Date(t.start), n = new Date(t.end), a = s.getHours() * 60 + s.getMinutes(), o = Math.max((n - s) / (1e3 * 60), r ? 20 : 30), c = this.getEventColor(t), l = this.getContrastingTextColor(c), d = r ? "4px 8px" : "8px 12px", p = r ? "11px" : "13px", v = r ? 2 : 12, y = r ? 2 : 24, S = r ? "4px" : "6px";
1443
1474
  let x, w;
1444
1475
  if (i && i.has(t.id)) {
1445
- const { column: C, totalColumns: E } = i.get(t.id), $ = `(100% - ${g + b}px)`;
1446
- x = `calc(${g}px + ${C} * ${$} / ${E})`, w = `calc(${$} / ${E})`;
1476
+ const { column: C, totalColumns: k } = i.get(t.id), $ = `(100% - ${v + y}px)`;
1477
+ x = `calc(${v}px + ${C} * ${$} / ${k})`, w = `calc(${$} / ${k})`;
1447
1478
  } else
1448
- x = `${g}px`, w = `calc(100% - ${g + b}px)`;
1479
+ x = `${v}px`, w = `calc(100% - ${v + y}px)`;
1449
1480
  return `
1450
1481
  <div class="fc-event fc-timed-event" data-event-id="${this.escapeHTML(t.id)}"
1451
1482
  style="position: absolute; top: ${a}px; height: ${o}px;
1452
1483
  left: ${x}; width: ${w};
1453
- background-color: ${c}; border-radius: ${T};
1484
+ background-color: ${c}; border-radius: ${S};
1454
1485
  padding: ${d}; font-size: ${p};
1455
1486
  font-weight: 500; color: ${l}; overflow: hidden;
1456
1487
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
@@ -1485,7 +1516,7 @@ class k {
1485
1516
  });
1486
1517
  }
1487
1518
  }
1488
- class _ extends k {
1519
+ class _ extends D {
1489
1520
  constructor(t, e) {
1490
1521
  super(t, e), this.maxEventsToShow = 3;
1491
1522
  }
@@ -1560,7 +1591,7 @@ class _ extends k {
1560
1591
  }), this.attachCommonEventHandlers();
1561
1592
  }
1562
1593
  }
1563
- class z extends k {
1594
+ class z extends D {
1564
1595
  constructor(t, e) {
1565
1596
  super(t, e), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1566
1597
  }
@@ -1699,7 +1730,7 @@ class z extends k {
1699
1730
  t && (t.scrollTop = 8 * this.hourHeight - 50, this._scrolled = !0);
1700
1731
  }
1701
1732
  }
1702
- class B extends k {
1733
+ class B extends D {
1703
1734
  constructor(t, e) {
1704
1735
  super(t, e), this.hourHeight = 60, this.totalHeight = 24 * this.hourHeight;
1705
1736
  }
@@ -1719,7 +1750,7 @@ class B extends k {
1719
1750
  const r = ((p = (d = this.stateManager) == null ? void 0 : d.getState()) == null ? void 0 : p.currentDate) || /* @__PURE__ */ new Date(), i = this._extractDayData(t, r);
1720
1751
  if (!i)
1721
1752
  return '<div style="padding: 20px; text-align: center; color: var(--fc-text-secondary);">No data available for day view.</div>';
1722
- const { dayDate: s, dayName: n, isToday: a, allDayEvents: o, timedEvents: c } = i, l = Array.from({ length: 24 }, (g, b) => b);
1753
+ const { dayDate: s, dayName: n, isToday: a, allDayEvents: o, timedEvents: c } = i, l = Array.from({ length: 24 }, (v, y) => y);
1723
1754
  return `
1724
1755
  <div class="fc-day-view" style="display: flex; flex-direction: column; height: 100%; background: var(--fc-background); overflow: hidden;">
1725
1756
  ${this._renderHeader(s, n, a)}
@@ -2082,12 +2113,12 @@ class A extends M {
2082
2113
  <div class="color-options" id="color-picker" role="radiogroup" aria-labelledby="color-label">
2083
2114
  ${this.config.colors.map(
2084
2115
  (t) => `
2085
- <button type="button"
2086
- class="color-btn ${t.color === this._formData.color ? "selected" : ""}"
2087
- style="background-color: ${t.color}"
2088
- data-color="${t.color}"
2089
- title="${t.label}"
2090
- aria-label="${t.label}"
2116
+ <button type="button"
2117
+ class="color-btn ${t.color === this._formData.color ? "selected" : ""}"
2118
+ style="background-color: ${f.sanitizeColor(t.color)}"
2119
+ data-color="${g.escapeHTML(t.color)}"
2120
+ title="${g.escapeHTML(t.label)}"
2121
+ aria-label="${g.escapeHTML(t.label)}"
2091
2122
  aria-checked="${t.color === this._formData.color ? "true" : "false"}"
2092
2123
  role="radio"></button>
2093
2124
  `
@@ -2121,7 +2152,7 @@ class A extends M {
2121
2152
  });
2122
2153
  }
2123
2154
  open(t = /* @__PURE__ */ new Date()) {
2124
- this.hasAttribute("open") || this.setAttribute("open", ""), this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this._formData.start = t, this._formData.end = new Date(t.getTime() + this.config.defaultDuration * 60 * 1e3), this._formData.title = "", this._formData.color = this.config.colors[0].color, this.startInput && (this.titleInput.value = "", this.startInput.value = this.formatDateForInput(this._formData.start), this.endInput.value = this.formatDateForInput(this._formData.end), this.updateColorSelection(), this._cleanupFocusTrap && this._cleanupFocusTrap(), this._cleanupFocusTrap = y.trapFocus(this.modalContent));
2155
+ this.hasAttribute("open") || this.setAttribute("open", ""), this.titleGroup.classList.remove("has-error"), this.endGroup.classList.remove("has-error"), this._formData.start = t, this._formData.end = new Date(t.getTime() + this.config.defaultDuration * 60 * 1e3), this._formData.title = "", this._formData.color = this.config.colors[0].color, this.startInput && (this.titleInput.value = "", this.startInput.value = this.formatDateForInput(this._formData.start), this.endInput.value = this.formatDateForInput(this._formData.end), this.updateColorSelection(), this._cleanupFocusTrap && this._cleanupFocusTrap(), this._cleanupFocusTrap = g.trapFocus(this.modalContent));
2125
2156
  }
2126
2157
  close() {
2127
2158
  this.removeAttribute("open"), this._cleanupFocusTrap && (this._cleanupFocusTrap(), this._cleanupFocusTrap = null);
@@ -2151,7 +2182,7 @@ class A extends M {
2151
2182
  }
2152
2183
  }
2153
2184
  customElements.get("forcecal-event-form") || customElements.define("forcecal-event-form", A);
2154
- const m = class m extends M {
2185
+ const b = class b extends M {
2155
2186
  static get observedAttributes() {
2156
2187
  return ["view", "date", "locale", "timezone", "week-starts-on", "height"];
2157
2188
  }
@@ -2256,7 +2287,7 @@ const m = class m extends M {
2256
2287
  if (t) {
2257
2288
  this._currentViewInstance && this._currentViewInstance.cleanup && this._currentViewInstance.cleanup();
2258
2289
  try {
2259
- const e = m.RENDERERS[this.currentView] || _, r = new e(t, this.stateManager);
2290
+ const e = b.RENDERERS[this.currentView] || _, r = new e(t, this.stateManager);
2260
2291
  r._viewType = this.currentView, this._currentViewInstance = r, r.render();
2261
2292
  } catch (e) {
2262
2293
  console.error("[ForceCalendar] Error switching view:", e);
@@ -2677,7 +2708,7 @@ const m = class m extends M {
2677
2708
  return `
2678
2709
  <div class="force-calendar">
2679
2710
  <div class="fc-error">
2680
- <p><strong>Error:</strong> ${y.escapeHTML(s.message || "An error occurred")}</p>
2711
+ <p><strong>Error:</strong> ${g.escapeHTML(s.message || "An error occurred")}</p>
2681
2712
  </div>
2682
2713
  </div>
2683
2714
  `;
@@ -2695,7 +2726,7 @@ const m = class m extends M {
2695
2726
  <button class="fc-nav-arrow" data-action="previous" title="Previous">
2696
2727
  ${this.getIcon("chevron-left")}
2697
2728
  </button>
2698
- <h2 class="fc-title">${y.escapeHTML(n)}</h2>
2729
+ <h2 class="fc-title">${g.escapeHTML(n)}</h2>
2699
2730
  <button class="fc-nav-arrow" data-action="next" title="Next">
2700
2731
  ${this.getIcon("chevron-right")}
2701
2732
  </button>
@@ -2740,7 +2771,7 @@ const m = class m extends M {
2740
2771
  return;
2741
2772
  this._currentViewInstance && (this._currentViewInstance.cleanup && this._currentViewInstance.cleanup(), this._viewUnsubscribe && (this._viewUnsubscribe(), this._viewUnsubscribe = null));
2742
2773
  try {
2743
- const i = m.RENDERERS[this.currentView] || _, s = new i(t, this.stateManager);
2774
+ const i = b.RENDERERS[this.currentView] || _, s = new i(t, this.stateManager);
2744
2775
  s._viewType = this.currentView, this._currentViewInstance = s, s.render();
2745
2776
  } catch (i) {
2746
2777
  console.error("[ForceCalendar] Error creating/rendering view:", i);
@@ -2850,21 +2881,21 @@ const m = class m extends M {
2850
2881
  this._busUnsubscribers.forEach((t) => t()), this._busUnsubscribers = [], this._stateUnsubscribe && (this._stateUnsubscribe(), this._stateUnsubscribe = null), this._currentViewInstance && this._currentViewInstance.cleanup && (this._currentViewInstance.cleanup(), this._currentViewInstance = null), this.stateManager && this.stateManager.destroy(), super.cleanup();
2851
2882
  }
2852
2883
  };
2853
- v(m, "RENDERERS", {
2884
+ m(b, "RENDERERS", {
2854
2885
  month: _,
2855
2886
  week: z,
2856
2887
  day: B
2857
2888
  });
2858
- let D = m;
2859
- customElements.get("forcecal-main") || customElements.define("forcecal-main", D);
2889
+ let E = b;
2890
+ customElements.get("forcecal-main") || customElements.define("forcecal-main", E);
2860
2891
  export {
2861
2892
  M as BaseComponent,
2862
- k as BaseViewRenderer,
2863
- y as DOMUtils,
2893
+ D as BaseViewRenderer,
2894
+ g as DOMUtils,
2864
2895
  u as DateUtils,
2865
2896
  B as DayViewRenderer,
2866
- S as EventBus,
2867
- D as ForceCalendar,
2897
+ T as EventBus,
2898
+ E as ForceCalendar,
2868
2899
  _ as MonthViewRenderer,
2869
2900
  V as StateManager,
2870
2901
  f as StyleUtils,