@glatam/calendar-ui 1.0.2 → 1.0.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.
Files changed (37) hide show
  1. package/dist/components/calendar-modal.d.ts +24 -0
  2. package/dist/components/calendar-modal.d.ts.map +1 -0
  3. package/dist/glatam-calendar-mini.d.ts +38 -0
  4. package/dist/glatam-calendar-mini.d.ts.map +1 -0
  5. package/dist/glatam-calendar.cjs +836 -0
  6. package/dist/glatam-calendar.d.ts +58 -0
  7. package/dist/glatam-calendar.d.ts.map +1 -0
  8. package/dist/glatam-calendar.js +1541 -0
  9. package/{src/index.ts → dist/index.d.ts} +1 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/styles/calendar-modal.css.d.ts +2 -0
  12. package/dist/styles/calendar-modal.css.d.ts.map +1 -0
  13. package/dist/styles/calendar.css.d.ts +2 -0
  14. package/dist/styles/calendar.css.d.ts.map +1 -0
  15. package/dist/styles/variables.css.d.ts +2 -0
  16. package/dist/styles/variables.css.d.ts.map +1 -0
  17. package/dist/utils/timezone-utils.d.ts +16 -0
  18. package/dist/utils/timezone-utils.d.ts.map +1 -0
  19. package/dist/views/day-view.d.ts +27 -0
  20. package/dist/views/day-view.d.ts.map +1 -0
  21. package/dist/views/month-view.d.ts +23 -0
  22. package/dist/views/month-view.d.ts.map +1 -0
  23. package/dist/views/week-view.d.ts +28 -0
  24. package/dist/views/week-view.d.ts.map +1 -0
  25. package/package.json +6 -2
  26. package/src/components/calendar-modal.ts +0 -163
  27. package/src/glatam-calendar-mini.ts +0 -147
  28. package/src/glatam-calendar.ts +0 -194
  29. package/src/styles/calendar-modal.css.ts +0 -158
  30. package/src/styles/calendar.css.ts +0 -259
  31. package/src/styles/variables.css.ts +0 -91
  32. package/src/utils/timezone-utils.ts +0 -87
  33. package/src/views/day-view.ts +0 -130
  34. package/src/views/month-view.ts +0 -117
  35. package/src/views/week-view.ts +0 -158
  36. package/tsconfig.json +0 -8
  37. package/vite.config.ts +0 -35
@@ -1,87 +0,0 @@
1
- import { TimeSlot } from '@glatam/calendar-core';
2
-
3
- /**
4
- * Calculates the difference in minutes between two timezones on a specific date.
5
- * 'local' represents the user's local timezone.
6
- */
7
- export function getTimezoneOffsetDiff(date: Date, fromTz: string, toTz: string): number {
8
- if (fromTz === toTz) return 0;
9
-
10
- const getTzDate = (d: Date, tz: string) => {
11
- const options: Intl.DateTimeFormatOptions = {
12
- year: 'numeric', month: 'numeric', day: 'numeric',
13
- hour: 'numeric', minute: 'numeric', second: 'numeric',
14
- hour12: false,
15
- };
16
- if (tz !== 'local') {
17
- options.timeZone = tz;
18
- }
19
- const formatter = new Intl.DateTimeFormat('en-US', options);
20
- const parts = formatter.formatToParts(d);
21
- const map = new Map(parts.map(p => [p.type, p.value]));
22
-
23
- const hourVal = Number(map.get('hour'));
24
- const hour = hourVal === 24 ? 0 : hourVal;
25
-
26
- return new Date(
27
- Number(map.get('year')),
28
- Number(map.get('month')) - 1,
29
- Number(map.get('day')),
30
- hour,
31
- Number(map.get('minute')),
32
- Number(map.get('second'))
33
- );
34
- };
35
-
36
- try {
37
- const d1 = getTzDate(date, fromTz);
38
- const d2 = getTzDate(date, toTz);
39
- return (d2.getTime() - d1.getTime()) / (60 * 1000);
40
- } catch (e) {
41
- // Fallback if timezone name is invalid
42
- return 0;
43
- }
44
- }
45
-
46
- /**
47
- * Shifts a single TimeSlot's times by a given offset in minutes and returns the shifted slot and optional day offset.
48
- */
49
- export function shiftSlot(slot: TimeSlot, offsetMinutes: number): { start: string; end: string; dayShift: number } {
50
- if (offsetMinutes === 0) {
51
- return { start: slot.start, end: slot.end, dayShift: 0 };
52
- }
53
-
54
- const parseToMinutes = (timeStr: string) => {
55
- const [h, m] = timeStr.split(':').map(Number);
56
- return h * 60 + m;
57
- };
58
-
59
- const formatFromMinutes = (totalMinutes: number) => {
60
- // Normalize minutes to [0, 1439]
61
- let normalized = totalMinutes % 1440;
62
- if (normalized < 0) normalized += 1440;
63
- const h = String(Math.floor(normalized / 60)).padStart(2, '0');
64
- const m = String(normalized % 60).padStart(2, '0');
65
- return `${h}:${m}`;
66
- };
67
-
68
- const startMin = parseToMinutes(slot.start);
69
- const endMin = parseToMinutes(slot.end);
70
-
71
- const shiftedStart = startMin + offsetMinutes;
72
- const shiftedEnd = endMin + offsetMinutes;
73
-
74
- // Determine if we crossed midnight (day shift)
75
- let dayShift = 0;
76
- if (shiftedStart < 0) {
77
- dayShift = -1;
78
- } else if (shiftedStart >= 1440) {
79
- dayShift = 1;
80
- }
81
-
82
- return {
83
- start: formatFromMinutes(shiftedStart),
84
- end: formatFromMinutes(shiftedEnd),
85
- dayShift
86
- };
87
- }
@@ -1,130 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property, state } from 'lit/decorators.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
5
-
6
- @customElement('glatam-calendar-day-view')
7
- export class GlatamCalendarDayView extends LitElement {
8
- static styles = css`
9
- :host {
10
- display: grid; grid-template-columns: var(--glatam-time-col-width) 1fr; gap: 2px;
11
- width: 100%; background-color: var(--glatam-border); border: 1px solid var(--glatam-border);
12
- border-radius: var(--glatam-grid-border-radius); overflow: hidden; user-select: none;
13
- }
14
- .time-col { background-color: var(--glatam-surface); display: flex; flex-direction: column; }
15
- .day-col { background-color: var(--glatam-bg); display: flex; flex-direction: column; }
16
- .header-cell {
17
- height: 60px; display: flex; flex-direction: column; align-items: center; justify-content: center;
18
- background-color: var(--glatam-surface); border-bottom: 2px solid var(--glatam-border);
19
- font-size: 0.875rem; font-weight: 500;
20
- }
21
- .header-cell.today { color: var(--glatam-primary); font-weight: 700; }
22
- .slot-cell {
23
- height: var(--glatam-slot-height); border-bottom: 1px solid var(--glatam-border);
24
- display: flex; align-items: center; justify-content: center; font-size: 0.75rem;
25
- cursor: pointer; transition: background-color var(--glatam-transition-fast); position: relative;
26
- }
27
- .time-slot-label { font-size: 0.7rem; color: var(--glatam-text-secondary); }
28
- .slot-cell.available:hover { background-color: var(--glatam-surface); }
29
- .slot-cell.blocked {
30
- background-color: var(--glatam-blocked-bg);
31
- background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, var(--glatam-blocked-stripe) 5px, var(--glatam-blocked-stripe) 10px);
32
- color: var(--glatam-blocked-text); cursor: not-allowed; font-weight: 600; padding: 0 10px;
33
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
34
- }
35
- .slot-cell.selected { background-color: var(--glatam-selection-bg); border-left: 3px solid var(--glatam-selection-border); }
36
- .slot-cell.drag-selecting { background-color: var(--glatam-primary-light); }
37
- `;
38
-
39
- @property({ type: Object }) day: CalendarDay | null = null;
40
- @property({ type: Array }) slots: TimeSlot[] = [];
41
- @property({ type: String }) locale = 'es';
42
- @property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
43
- @property({ type: String }) role = 'provider';
44
-
45
- @state() private isDragging = false;
46
- @state() private dragStartSlotIndex: number | null = null;
47
- @state() private dragEndSlotIndex: number | null = null;
48
-
49
- connectedCallback() { super.connectedCallback(); window.addEventListener('mouseup', this.handleMouseUp); }
50
- disconnectedCallback() { window.removeEventListener('mouseup', this.handleMouseUp); super.disconnectedCallback(); }
51
-
52
- private handleMouseDown(slotIndex: number, isBlocked: boolean) {
53
- if (isBlocked && this.day) {
54
- if (this.role === 'buyer') return;
55
- this.dispatchEvent(new CustomEvent('slot-click', {
56
- detail: { dateString: this.day.dateString, slot: this.day.slots[slotIndex], isBlocked: true },
57
- bubbles: true, composed: true
58
- }));
59
- return;
60
- }
61
- this.isDragging = true; this.dragStartSlotIndex = slotIndex; this.dragEndSlotIndex = slotIndex;
62
- }
63
-
64
- private handleMouseEnter(slotIndex: number, isBlocked: boolean) {
65
- if (this.isDragging && !isBlocked) this.dragEndSlotIndex = slotIndex;
66
- }
67
-
68
- private handleMouseUp = () => {
69
- if (!this.isDragging || !this.day || this.dragStartSlotIndex === null || this.dragEndSlotIndex === null) {
70
- this.isDragging = false; return;
71
- }
72
- this.isDragging = false;
73
- const startIdx = Math.min(this.dragStartSlotIndex, this.dragEndSlotIndex);
74
- const endIdx = Math.max(this.dragStartSlotIndex, this.dragEndSlotIndex);
75
- const rangeSlots = this.day.slots.slice(startIdx, endIdx + 1);
76
- if (!rangeSlots.some(s => s.isBlocked)) {
77
- this.dispatchEvent(new CustomEvent('range-select', {
78
- detail: { dateString: this.day.dateString, start: rangeSlots[0].start, end: rangeSlots[rangeSlots.length - 1].end },
79
- bubbles: true, composed: true
80
- }));
81
- }
82
- this.dragStartSlotIndex = null; this.dragEndSlotIndex = null;
83
- };
84
-
85
- private isSlotSelected(slot: TimeSlot): boolean {
86
- if (!this.day || !this.selectedRange || this.selectedRange.dateString !== this.day.dateString) return false;
87
- return slot.start >= this.selectedRange.start && slot.end <= this.selectedRange.end;
88
- }
89
-
90
- private isSlotDragSelecting(slotIndex: number): boolean {
91
- if (!this.isDragging) return false;
92
- const start = Math.min(this.dragStartSlotIndex!, this.dragEndSlotIndex!), end = Math.max(this.dragStartSlotIndex!, this.dragEndSlotIndex!);
93
- return slotIndex >= start && slotIndex <= end;
94
- }
95
-
96
- render() {
97
- if (!this.day) return html`<div>Cargando...</div>`;
98
- const isToday = this.day.dateString === new Date().toISOString().split('T')[0];
99
- const formattedDate = new Intl.DateTimeFormat(this.locale, { weekday: 'long', day: 'numeric', month: 'long' }).format(this.day.date);
100
-
101
- return html`
102
- <div class="time-col">
103
- <div class="header-cell">Hora</div>
104
- ${this.slots.map(slot => html`<div class="slot-cell time-slot-label">${slot.start}</div>`)}
105
- </div>
106
-
107
- <div class="day-col">
108
- <div class="header-cell ${isToday ? 'today' : ''}">
109
- <div style="text-transform: capitalize;">${formattedDate}</div>
110
- </div>
111
-
112
- ${this.day.slots.map((slot, sIdx) => {
113
- const blocked = slot.isBlocked, selected = this.isSlotSelected(slot), dragSelecting = this.isSlotDragSelecting(sIdx);
114
- return html`
115
- <div
116
- class=${classMap({
117
- 'slot-cell': true, 'available': !blocked, 'blocked': blocked, 'selected': selected, 'drag-selecting': dragSelecting
118
- })}
119
- @mousedown=${() => this.handleMouseDown(sIdx, blocked)}
120
- @mouseenter=${() => this.handleMouseEnter(sIdx, blocked)}
121
- title=${blocked && slot.rule?.description ? slot.rule.description : ''}
122
- >
123
- ${blocked ? (slot.rule?.description || 'Ocupado') : selected ? 'Reservado' : ''}
124
- </div>
125
- `;
126
- })}
127
- </div>
128
- `;
129
- }
130
- }
@@ -1,117 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import { CalendarDay } from '@glatam/calendar-core';
5
-
6
- @customElement('glatam-calendar-month-view')
7
- export class GlatamCalendarMonthView extends LitElement {
8
- static styles = css`
9
- :host { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; width: 100%; }
10
- .weekday { font-size: 0.75rem; font-weight: 600; color: var(--glatam-text-secondary); text-transform: uppercase; text-align: center; padding: 8px 0; letter-spacing: 0.05em; }
11
- .day-cell {
12
- min-height: var(--glatam-day-min-height); border: 1px solid var(--glatam-border); border-radius: var(--glatam-grid-border-radius);
13
- padding: 8px; display: flex; flex-direction: column; justify-content: space-between; cursor: pointer;
14
- transition: background-color var(--glatam-transition-fast), border-color var(--glatam-transition-fast); background-color: var(--glatam-bg); position: relative;
15
- }
16
- .day-cell:hover:not(.blocked):not(.disabled) { background-color: var(--glatam-surface); border-color: var(--glatam-text-secondary); }
17
- .day-cell.padding { opacity: 0.4; }
18
- .day-cell.blocked {
19
- background: repeating-linear-gradient(45deg, var(--glatam-blocked-bg), var(--glatam-blocked-bg) 10px, var(--glatam-blocked-stripe) 10px, var(--glatam-blocked-stripe) 20px);
20
- border-color: var(--glatam-blocked-border); color: var(--glatam-blocked-text); cursor: not-allowed;
21
- }
22
- .day-cell.today { background-color: var(--glatam-today-bg); border-color: var(--glatam-primary); }
23
- .day-cell.selected { background-color: var(--glatam-selection-bg); border-color: var(--glatam-selection-border); box-shadow: inset 0 0 0 1px var(--glatam-selection-border); }
24
- .day-cell.disabled { opacity: 0.2; cursor: not-allowed; background-color: var(--glatam-surface); border-color: var(--glatam-border); pointer-events: none; }
25
- .day-cell.empty { border: none; background: transparent; cursor: default; pointer-events: none; }
26
- .day-number { font-size: 0.875rem; font-weight: 500; margin-bottom: 4px; }
27
- .day-cell.today .day-number { color: var(--glatam-today-text); font-weight: 700; }
28
- .slot-indicator { display: flex; flex-direction: column; gap: 3px; font-size: 0.7rem; overflow: hidden; margin-top: 4px; }
29
- .badge { padding: 2px 6px; border-radius: 4px; font-weight: 500; text-align: center; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
30
- .badge.blocked-day { background-color: rgba(255, 69, 58, 0.12); color: #ff453a; font-weight: 600; }
31
- .badge.blocked-slot { background-color: var(--glatam-surface); border: 1px solid var(--glatam-border); color: var(--glatam-text-secondary); }
32
- :host([size="small"]) .day-cell { min-height: 38px; padding: 4px; justify-content: center; align-items: center; }
33
- :host([size="small"]) .slot-indicator { display: none; }
34
- :host([size="small"]) .day-number { margin-bottom: 0; font-size: 0.8rem; }
35
- @media (max-width: 600px) {
36
- .day-cell { min-height: 38px; padding: 4px; justify-content: center; align-items: center; }
37
- .slot-indicator { display: none; }
38
- .day-number { margin-bottom: 0; font-size: 0.8rem; }
39
- }
40
- `;
41
-
42
- @property({ type: Array }) days: CalendarDay[] = [];
43
- @property({ type: String }) locale = 'es';
44
- @property({ type: Number }) startOfWeekDay = 0;
45
- @property({ type: Array }) selectedDates: string[] = [];
46
- @property({ type: String }) role = 'provider';
47
- @property({ type: String, reflect: true }) size = 'medium';
48
- @property({ type: String }) minDate = '';
49
- @property({ type: String }) maxDate = '';
50
- @property({ type: Boolean }) showNeighboringMonth = true;
51
- @property({ attribute: false }) tileClassName: ((data: { date: Date; dateString: string }) => string) | null = null;
52
-
53
- private getWeekdayNames(): string[] {
54
- const names: string[] = [];
55
- const baseDate = new Date(2026, 6, 12 + this.startOfWeekDay);
56
- const formatter = new Intl.DateTimeFormat(this.locale, { weekday: 'short' });
57
- for (let i = 0; i < 7; i++) {
58
- names.push(formatter.format(baseDate));
59
- baseDate.setDate(baseDate.getDate() + 1);
60
- }
61
- return names;
62
- }
63
-
64
- private handleDayClick(day: CalendarDay) {
65
- const isDisabled = (this.minDate && day.dateString < this.minDate) || (this.maxDate && day.dateString > this.maxDate);
66
- if (this.role === 'buyer' && (day.isBlocked || isDisabled)) return;
67
- this.dispatchEvent(new CustomEvent('day-select', {
68
- detail: { dateString: day.dateString, isBlocked: day.isBlocked },
69
- bubbles: true, composed: true
70
- }));
71
- }
72
-
73
- render() {
74
- const weekdayNames = this.getWeekdayNames();
75
-
76
- return html`
77
- ${weekdayNames.map(name => html`<div class="weekday">${name}</div>`)}
78
-
79
- ${this.days.map(day => {
80
- if (!day.isCurrentMonth && !this.showNeighboringMonth) {
81
- return html`<div class="day-cell empty"></div>`;
82
- }
83
-
84
- const isToday = day.dateString === new Date().toISOString().split('T')[0];
85
- const isSelected = this.selectedDates.includes(day.dateString);
86
- const blockedSlots = day.slots.filter(s => s.isBlocked);
87
- const isDisabled = (this.minDate && day.dateString < this.minDate) || (this.maxDate && day.dateString > this.maxDate);
88
- const customClass = this.tileClassName ? this.tileClassName({ date: day.date, dateString: day.dateString }) : '';
89
-
90
- return html`
91
- <div
92
- class=${classMap({
93
- 'day-cell': true, 'padding': !day.isCurrentMonth, 'blocked': day.isBlocked, 'today': isToday, 'selected': isSelected, 'disabled': !!isDisabled, [customClass]: !!customClass
94
- })}
95
- part="day-cell ${customClass}"
96
- @click=${() => this.handleDayClick(day)}
97
- >
98
- <div class="day-number">${day.dayNumber}</div>
99
-
100
- <div class="slot-indicator">
101
- ${day.isBlocked
102
- ? html`<div class="badge blocked-day">${day.rule?.description || 'Bloqueado'}</div>`
103
- : blockedSlots.slice(0, 2).map(slot => html`
104
- <div class="badge blocked-slot" title=${slot.rule?.description || ''}>
105
- 🚫 ${slot.rule?.description || slot.start}
106
- </div>
107
- `)}
108
- ${!day.isBlocked && blockedSlots.length > 2
109
- ? html`<div style="text-align: center; font-size: 0.65rem; color: var(--glatam-text-secondary);">+${blockedSlots.length - 2} tareas</div>`
110
- : ''}
111
- </div>
112
- </div>
113
- `;
114
- })}
115
- `;
116
- }
117
- }
@@ -1,158 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property, state } from 'lit/decorators.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
5
-
6
- @customElement('glatam-calendar-week-view')
7
- export class GlatamCalendarWeekView extends LitElement {
8
- static styles = css`
9
- :host {
10
- display: grid; grid-template-columns: var(--glatam-time-col-width) repeat(7, 1fr); gap: 2px;
11
- width: 100%; background-color: var(--glatam-border); border: 1px solid var(--glatam-border);
12
- border-radius: var(--glatam-grid-border-radius); overflow: hidden; user-select: none;
13
- }
14
- @media (max-width: 600px) {
15
- :host {
16
- grid-template-columns: 40px repeat(7, 1fr);
17
- overflow-x: hidden;
18
- }
19
- .slot-cell {
20
- font-size: 0.65rem;
21
- }
22
- .slot-cell.blocked, .slot-cell.selected {
23
- font-size: 0;
24
- padding: 0;
25
- }
26
- .time-slot-label {
27
- font-size: 0.65rem;
28
- }
29
- .header-cell {
30
- font-size: 0.7rem;
31
- }
32
- .header-cell .day-num {
33
- font-size: 0.8rem;
34
- }
35
- }
36
- .time-col { background-color: var(--glatam-surface); display: flex; flex-direction: column; }
37
- .day-col { background-color: var(--glatam-bg); display: flex; flex-direction: column; }
38
- .header-cell {
39
- height: 60px; display: flex; flex-direction: column; align-items: center; justify-content: center;
40
- background-color: var(--glatam-surface); border-bottom: 2px solid var(--glatam-border);
41
- font-size: 0.8rem; font-weight: 500; text-transform: capitalize;
42
- }
43
- .header-cell.today { color: var(--glatam-primary); font-weight: 700; }
44
- .header-cell .day-num { font-size: 0.95rem; font-weight: 600; margin-top: 2px; }
45
- .slot-cell {
46
- height: var(--glatam-slot-height); border-bottom: 1px solid var(--glatam-border);
47
- display: flex; align-items: center; justify-content: center; font-size: 0.75rem;
48
- cursor: pointer; transition: background-color var(--glatam-transition-fast); position: relative;
49
- }
50
- .time-slot-label { font-size: 0.7rem; color: var(--glatam-text-secondary); }
51
- .slot-cell.available:hover { background-color: var(--glatam-surface); }
52
- .slot-cell.blocked {
53
- background-color: var(--glatam-blocked-bg);
54
- background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, var(--glatam-blocked-stripe) 5px, var(--glatam-blocked-stripe) 10px);
55
- color: var(--glatam-blocked-text); cursor: not-allowed; font-weight: 600; padding: 0 4px;
56
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center;
57
- }
58
- .slot-cell.selected { background-color: var(--glatam-selection-bg); border-left: 3px solid var(--glatam-selection-border); }
59
- .slot-cell.drag-selecting { background-color: var(--glatam-primary-light); }
60
- `;
61
-
62
- @property({ type: Array }) days: CalendarDay[] = [];
63
- @property({ type: Array }) slots: TimeSlot[] = [];
64
- @property({ type: String }) locale = 'es';
65
- @property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
66
- @property({ type: String }) role = 'provider';
67
-
68
- @state() private isDragging = false;
69
- @state() private dragDayIndex: number | null = null;
70
- @state() private dragStartSlotIndex: number | null = null;
71
- @state() private dragEndSlotIndex: number | null = null;
72
-
73
- connectedCallback() { super.connectedCallback(); window.addEventListener('mouseup', this.handleMouseUp); }
74
- disconnectedCallback() { window.removeEventListener('mouseup', this.handleMouseUp); super.disconnectedCallback(); }
75
-
76
- private handleMouseDown(dayIndex: number, slotIndex: number, isBlocked: boolean) {
77
- if (isBlocked) {
78
- if (this.role === 'buyer') return;
79
- this.dispatchEvent(new CustomEvent('slot-click', {
80
- detail: { dateString: this.days[dayIndex].dateString, slot: this.days[dayIndex].slots[slotIndex], isBlocked: true },
81
- bubbles: true, composed: true
82
- }));
83
- return;
84
- }
85
- this.isDragging = true; this.dragDayIndex = dayIndex; this.dragStartSlotIndex = slotIndex; this.dragEndSlotIndex = slotIndex;
86
- }
87
-
88
- private handleMouseEnter(dayIndex: number, slotIndex: number, isBlocked: boolean) {
89
- if (this.isDragging && dayIndex === this.dragDayIndex && !isBlocked) this.dragEndSlotIndex = slotIndex;
90
- }
91
-
92
- private handleMouseUp = () => {
93
- if (!this.isDragging || this.dragDayIndex === null || this.dragStartSlotIndex === null || this.dragEndSlotIndex === null) {
94
- this.isDragging = false; return;
95
- }
96
- this.isDragging = false;
97
- const startIdx = Math.min(this.dragStartSlotIndex, this.dragEndSlotIndex);
98
- const endIdx = Math.max(this.dragStartSlotIndex, this.dragEndSlotIndex);
99
- const day = this.days[this.dragDayIndex], rangeSlots = day.slots.slice(startIdx, endIdx + 1);
100
- if (!rangeSlots.some(s => s.isBlocked)) {
101
- this.dispatchEvent(new CustomEvent('range-select', {
102
- detail: { dateString: day.dateString, start: rangeSlots[0].start, end: rangeSlots[rangeSlots.length - 1].end },
103
- bubbles: true, composed: true
104
- }));
105
- }
106
- this.dragDayIndex = null; this.dragStartSlotIndex = null; this.dragEndSlotIndex = null;
107
- };
108
-
109
- private isSlotSelected(dayString: string, slot: TimeSlot): boolean {
110
- if (!this.selectedRange || this.selectedRange.dateString !== dayString) return false;
111
- return slot.start >= this.selectedRange.start && slot.end <= this.selectedRange.end;
112
- }
113
-
114
- private isSlotDragSelecting(dayIndex: number, slotIndex: number): boolean {
115
- if (!this.isDragging || dayIndex !== this.dragDayIndex) return false;
116
- const start = Math.min(this.dragStartSlotIndex!, this.dragEndSlotIndex!), end = Math.max(this.dragStartSlotIndex!, this.dragEndSlotIndex!);
117
- return slotIndex >= start && slotIndex <= end;
118
- }
119
-
120
- render() {
121
- const todayStr = new Date().toISOString().split('T')[0];
122
-
123
- return html`
124
- <div class="time-col">
125
- <div class="header-cell">Hora</div>
126
- ${this.slots.map(slot => html`<div class="slot-cell time-slot-label">${slot.start}</div>`)}
127
- </div>
128
-
129
- ${this.days.map((day, dIdx) => {
130
- const isToday = day.dateString === todayStr, dayLabel = new Intl.DateTimeFormat(this.locale, { weekday: 'short' }).format(day.date);
131
- return html`
132
- <div class="day-col">
133
- <div class="header-cell ${isToday ? 'today' : ''}">
134
- <div>${dayLabel}</div>
135
- <div class="day-num">${day.dayNumber}</div>
136
- </div>
137
-
138
- ${day.slots.map((slot, sIdx) => {
139
- const blocked = slot.isBlocked, selected = this.isSlotSelected(day.dateString, slot), dragSelecting = this.isSlotDragSelecting(dIdx, sIdx);
140
- return html`
141
- <div
142
- class=${classMap({
143
- 'slot-cell': true, 'available': !blocked, 'blocked': blocked, 'selected': selected, 'drag-selecting': dragSelecting
144
- })}
145
- @mousedown=${() => this.handleMouseDown(dIdx, sIdx, blocked)}
146
- @mouseenter=${() => this.handleMouseEnter(dIdx, sIdx, blocked)}
147
- title=${blocked && slot.rule?.description ? slot.rule.description : ''}
148
- >
149
- ${blocked ? (slot.rule?.description || 'Ocupado') : selected ? 'Reservado' : ''}
150
- </div>
151
- `;
152
- })}
153
- </div>
154
- `;
155
- })}
156
- `;
157
- }
158
- }
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"]
8
- }
package/vite.config.ts DELETED
@@ -1,35 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import dts from 'vite-plugin-dts';
3
- import { resolve } from 'path';
4
-
5
- export default defineConfig({
6
- build: {
7
- lib: {
8
- entry: resolve(__dirname, 'src/index.ts'),
9
- name: 'GlatamCalendar',
10
- fileName: (format) => `glatam-calendar.${format === 'es' ? 'js' : 'cjs'}`,
11
- formats: ['es', 'cjs'],
12
- },
13
- rollupOptions: {
14
- external: [
15
- 'lit',
16
- 'lit/decorators.js',
17
- 'lit/directives/class-map.js',
18
- 'lit/directives/style-map.js',
19
- '@glatam/calendar-core',
20
- ],
21
- output: {
22
- globals: {
23
- lit: 'Lit',
24
- '@glatam/calendar-core': 'GlatamCalendarCore',
25
- },
26
- },
27
- },
28
- },
29
- plugins: [
30
- dts({
31
- insertTypesEntry: true,
32
- tsconfigPath: './tsconfig.json',
33
- }),
34
- ],
35
- });