@glatam/calendar-ui 1.0.0

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.
@@ -0,0 +1,10 @@
1
+ export * from './glatam-calendar.js';
2
+ export * from './glatam-calendar-mini.js';
3
+ export * from './views/month-view.js';
4
+ export * from './views/week-view.js';
5
+ export * from './views/day-view.js';
6
+ export * from './components/calendar-modal.js';
7
+ export * from './styles/variables.css.js';
8
+ export * from './styles/calendar.css.js';
9
+ export { formatISODate, parseISODate, isTimeBlocked } from '@glatam/calendar-core';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const calendarStyles: import('lit').CSSResult;
2
+ //# sourceMappingURL=calendar.css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.css.d.ts","sourceRoot":"","sources":["../../src/styles/calendar.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,yBAgQ1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const variablesStyles: import('lit').CSSResult;
2
+ //# sourceMappingURL=variables.css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables.css.d.ts","sourceRoot":"","sources":["../../src/styles/variables.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,yBAwF3B,CAAC"}
@@ -0,0 +1,16 @@
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 declare function getTimezoneOffsetDiff(date: Date, fromTz: string, toTz: string): number;
8
+ /**
9
+ * Shifts a single TimeSlot's times by a given offset in minutes and returns the shifted slot and optional day offset.
10
+ */
11
+ export declare function shiftSlot(slot: TimeSlot, offsetMinutes: number): {
12
+ start: string;
13
+ end: string;
14
+ dayShift: number;
15
+ };
16
+ //# sourceMappingURL=timezone-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timezone-utils.d.ts","sourceRoot":"","sources":["../../src/utils/timezone-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAqCtF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAsCjH"}
@@ -0,0 +1,27 @@
1
+ import { LitElement } from 'lit';
2
+ import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
3
+
4
+ export declare class GlatamCalendarDayView extends LitElement {
5
+ static styles: import('lit').CSSResult;
6
+ day: CalendarDay | null;
7
+ slots: TimeSlot[];
8
+ locale: string;
9
+ selectedRange: {
10
+ dateString: string;
11
+ start: string;
12
+ end: string;
13
+ } | null;
14
+ role: string;
15
+ private isDragging;
16
+ private dragStartSlotIndex;
17
+ private dragEndSlotIndex;
18
+ connectedCallback(): void;
19
+ disconnectedCallback(): void;
20
+ private handleMouseDown;
21
+ private handleMouseEnter;
22
+ private handleMouseUp;
23
+ private isSlotSelected;
24
+ private isSlotDragSelecting;
25
+ render(): import('lit-html').TemplateResult<1>;
26
+ }
27
+ //# sourceMappingURL=day-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day-view.d.ts","sourceRoot":"","sources":["../../src/views/day-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBACa,qBAAsB,SAAQ,UAAU;IACnD,MAAM,CAAC,MAAM,0BA6BX;IAE0B,GAAG,EAAE,WAAW,GAAG,IAAI,CAAQ;IAChC,KAAK,EAAE,QAAQ,EAAE,CAAM;IACtB,MAAM,SAAQ;IACd,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,IAAI,SAAc;IAErC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAExD,iBAAiB;IACjB,oBAAoB;IAEpB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa,CAenB;IAEF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,mBAAmB;IAM3B,MAAM;CAkCP"}
@@ -0,0 +1,23 @@
1
+ import { LitElement } from 'lit';
2
+ import { CalendarDay } from '@glatam/calendar-core';
3
+
4
+ export declare class GlatamCalendarMonthView extends LitElement {
5
+ static styles: import('lit').CSSResult;
6
+ days: CalendarDay[];
7
+ locale: string;
8
+ startOfWeekDay: number;
9
+ selectedDates: string[];
10
+ role: string;
11
+ size: string;
12
+ minDate: string;
13
+ maxDate: string;
14
+ showNeighboringMonth: boolean;
15
+ tileClassName: ((data: {
16
+ date: Date;
17
+ dateString: string;
18
+ }) => string) | null;
19
+ private getWeekdayNames;
20
+ private handleDayClick;
21
+ render(): import('lit-html').TemplateResult<1>;
22
+ }
23
+ //# sourceMappingURL=month-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"month-view.d.ts","sourceRoot":"","sources":["../../src/views/month-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,qBACa,uBAAwB,SAAQ,UAAU;IACrD,MAAM,CAAC,MAAM,0BAgCX;IAEyB,IAAI,EAAE,WAAW,EAAE,CAAM;IACxB,MAAM,SAAQ;IACd,cAAc,SAAK;IACpB,aAAa,EAAE,MAAM,EAAE,CAAM;IAC5B,IAAI,SAAc;IACH,IAAI,SAAY;IAC/B,OAAO,SAAM;IACb,OAAO,SAAM;IACZ,oBAAoB,UAAQ;IACzB,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,GAAG,IAAI,CAAQ;IAEpH,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,cAAc;IAQtB,MAAM;CA4CP"}
@@ -0,0 +1,28 @@
1
+ import { LitElement } from 'lit';
2
+ import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
3
+
4
+ export declare class GlatamCalendarWeekView extends LitElement {
5
+ static styles: import('lit').CSSResult;
6
+ days: CalendarDay[];
7
+ slots: TimeSlot[];
8
+ locale: string;
9
+ selectedRange: {
10
+ dateString: string;
11
+ start: string;
12
+ end: string;
13
+ } | null;
14
+ role: string;
15
+ private isDragging;
16
+ private dragDayIndex;
17
+ private dragStartSlotIndex;
18
+ private dragEndSlotIndex;
19
+ connectedCallback(): void;
20
+ disconnectedCallback(): void;
21
+ private handleMouseDown;
22
+ private handleMouseEnter;
23
+ private handleMouseUp;
24
+ private isSlotSelected;
25
+ private isSlotDragSelecting;
26
+ render(): import('lit-html').TemplateResult<1>;
27
+ }
28
+ //# sourceMappingURL=week-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"week-view.d.ts","sourceRoot":"","sources":["../../src/views/week-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBACa,sBAAuB,SAAQ,UAAU;IACpD,MAAM,CAAC,MAAM,0BAoDX;IAEyB,IAAI,EAAE,WAAW,EAAE,CAAM;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAM;IACtB,MAAM,SAAQ;IACd,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,IAAI,SAAc;IAErC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAExD,iBAAiB;IACjB,oBAAoB;IAEpB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa,CAenB;IAEF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,mBAAmB;IAM3B,MAAM;CAsCP"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@glatam/calendar-ui",
3
+ "version": "1.0.0",
4
+ "description": "Componente web UI para el calendario @glatam/calendar",
5
+ "type": "module",
6
+ "author": "Santiago Hernández Saldarriaga",
7
+ "license": "MIT",
8
+ "main": "./dist/glatam-calendar.js",
9
+ "module": "./dist/glatam-calendar.js",
10
+ "types": "./dist/glatam-calendar.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/glatam-calendar.d.ts",
14
+ "import": "./dist/glatam-calendar.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "vite build",
19
+ "dev": "vite"
20
+ },
21
+ "dependencies": {
22
+ "@glatam/calendar-core": "^1.0.0",
23
+ "lit": "^3.1.2"
24
+ },
25
+ "devDependencies": {
26
+ "vite": "^5.1.4",
27
+ "vite-plugin-dts": "^3.7.3"
28
+ }
29
+ }
@@ -0,0 +1,297 @@
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 { BlockingRule } from '@glatam/calendar-core';
5
+
6
+ @customElement('glatam-calendar-modal')
7
+ export class GlatamCalendarModal extends LitElement {
8
+ static styles = css`
9
+ .modal-overlay {
10
+ position: fixed;
11
+ top: 0; left: 0; right: 0; bottom: 0;
12
+ background: rgba(0, 0, 0, 0.35);
13
+ backdrop-filter: blur(10px);
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ z-index: 1000;
18
+ opacity: 0; pointer-events: none;
19
+ transition: opacity var(--glatam-transition-normal);
20
+ }
21
+ .modal-overlay.open {
22
+ opacity: 1; pointer-events: auto;
23
+ }
24
+ .modal-content {
25
+ background: var(--glatam-bg);
26
+ color: var(--glatam-text);
27
+ border-radius: 24px;
28
+ padding: 28px;
29
+ width: 90%;
30
+ max-width: 400px;
31
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.12);
32
+ border: 1px solid var(--glatam-border);
33
+ transform: scale(0.92) translateY(10px);
34
+ transition: transform var(--glatam-transition-normal), background-color var(--glatam-transition-normal);
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 18px;
38
+ }
39
+ .modal-overlay.open .modal-content {
40
+ transform: scale(1) translateY(0);
41
+ }
42
+ h3 { margin: 0; font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; }
43
+ .form-group { display: flex; flex-direction: column; gap: 6px; }
44
+ label { font-size: 0.8rem; color: var(--glatam-text-secondary); font-weight: 600; }
45
+ input[type="text"] {
46
+ background: var(--glatam-surface);
47
+ border: 1px solid var(--glatam-border);
48
+ border-radius: 10px;
49
+ padding: 10px 14px;
50
+ color: var(--glatam-text);
51
+ font-family: inherit;
52
+ font-size: 0.9rem;
53
+ outline: none;
54
+ transition: border-color var(--glatam-transition-fast);
55
+ }
56
+ input[type="text"]:focus {
57
+ border-color: var(--glatam-primary);
58
+ }
59
+ .switch-row {
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ padding: 4px 0;
64
+ }
65
+
66
+ /* Apple Switch Style */
67
+ .switch {
68
+ position: relative;
69
+ display: inline-block;
70
+ width: 46px;
71
+ height: 26px;
72
+ }
73
+ .switch input { opacity: 0; width: 0; height: 0; }
74
+ .slider {
75
+ position: absolute;
76
+ cursor: pointer;
77
+ top: 0; left: 0; right: 0; bottom: 0;
78
+ background-color: var(--glatam-border);
79
+ transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
80
+ border-radius: 26px;
81
+ }
82
+ .slider:before {
83
+ position: absolute;
84
+ content: "";
85
+ height: 20px;
86
+ width: 20px;
87
+ left: 3px;
88
+ bottom: 3px;
89
+ background-color: white;
90
+ transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
91
+ border-radius: 50%;
92
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
93
+ }
94
+ input:checked + .slider {
95
+ background-color: var(--glatam-primary);
96
+ }
97
+ input:checked + .slider:before {
98
+ transform: translateX(20px);
99
+ }
100
+
101
+ .days-grid {
102
+ display: grid;
103
+ grid-template-columns: repeat(7, 1fr);
104
+ gap: 6px;
105
+ margin-top: 4px;
106
+ }
107
+ .day-btn {
108
+ width: 36px;
109
+ height: 36px;
110
+ margin: 0 auto;
111
+ border-radius: 50%;
112
+ border: 1px solid var(--glatam-border);
113
+ background: var(--glatam-bg);
114
+ color: var(--glatam-text);
115
+ font-size: 0.8rem;
116
+ font-weight: 600;
117
+ cursor: pointer;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ transition: background-color var(--glatam-transition-fast), color var(--glatam-transition-fast), border-color var(--glatam-transition-fast);
122
+ }
123
+ .day-btn:hover {
124
+ background-color: var(--glatam-surface);
125
+ }
126
+ .day-btn.selected {
127
+ background: var(--glatam-primary);
128
+ color: var(--glatam-text-light);
129
+ border-color: var(--glatam-primary);
130
+ }
131
+ .btn-actions { display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end; }
132
+ .btn {
133
+ padding: 10px 18px;
134
+ border-radius: 20px;
135
+ font-size: 0.875rem;
136
+ font-weight: 600;
137
+ cursor: pointer;
138
+ border: none;
139
+ transition: opacity var(--glatam-transition-fast), transform var(--glatam-transition-fast);
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ }
144
+ .btn:active { transform: scale(0.97); }
145
+ .btn-cancel { background: transparent; color: var(--glatam-text); border: 1px solid var(--glatam-border); }
146
+ .btn-cancel:hover { background-color: var(--glatam-surface); }
147
+ .btn-save { background: var(--glatam-primary); color: var(--glatam-text-light); }
148
+ .btn-save:hover { opacity: 0.95; }
149
+ .btn-danger {
150
+ background: rgba(255, 69, 58, 0.12);
151
+ color: #ff453a;
152
+ margin-right: auto;
153
+ }
154
+ .btn-danger:hover {
155
+ background: rgba(255, 69, 58, 0.18);
156
+ }
157
+ `;
158
+
159
+ @property({ type: Boolean }) open = false;
160
+ @property({ type: String }) dateString = '';
161
+ @property({ type: String }) startTime = '';
162
+ @property({ type: String }) endTime = '';
163
+ @property({ type: Boolean }) isRange = false;
164
+ @property({ type: Object }) existingRule: BlockingRule | null = null;
165
+
166
+ @state() private description = '';
167
+ @state() private blockAllDay = true;
168
+ @state() private isRecurring = false;
169
+ @state() private selectedDays: number[] = [];
170
+
171
+ willUpdate(changedProperties: Map<string, any>) {
172
+ if (changedProperties.has('open') && this.open) {
173
+ if (this.existingRule) {
174
+ this.description = 'Bloqueo';
175
+ this.blockAllDay = !this.existingRule.slots || this.existingRule.slots.length === 0;
176
+ this.isRecurring = this.existingRule.type === 'weekly';
177
+ this.selectedDays = this.existingRule.daysOfWeek || [];
178
+ } else {
179
+ this.description = '';
180
+ this.blockAllDay = !this.isRange;
181
+ this.isRecurring = false;
182
+ this.selectedDays = [new Date(this.dateString + 'T00:00:00').getDay()];
183
+ }
184
+ }
185
+ }
186
+
187
+ private toggleDay(dayNum: number) {
188
+ const idx = this.selectedDays.indexOf(dayNum);
189
+ if (idx > -1) {
190
+ this.selectedDays = this.selectedDays.filter(d => d !== dayNum);
191
+ } else {
192
+ this.selectedDays = [...this.selectedDays, dayNum];
193
+ }
194
+ }
195
+
196
+ private handleSave() {
197
+ const detail = {
198
+ title: this.description || 'Bloqueo',
199
+ blockAllDay: this.blockAllDay,
200
+ isRecurring: this.isRecurring,
201
+ selectedDays: this.selectedDays,
202
+ dateString: this.dateString,
203
+ startTime: this.startTime,
204
+ endTime: this.endTime
205
+ };
206
+ this.dispatchEvent(new CustomEvent('save-rule', { detail, bubbles: true, composed: true }));
207
+ }
208
+
209
+ private handleDelete() {
210
+ if (this.existingRule) {
211
+ this.dispatchEvent(new CustomEvent('delete-rule', {
212
+ detail: { id: this.existingRule.id },
213
+ bubbles: true,
214
+ composed: true
215
+ }));
216
+ }
217
+ }
218
+
219
+ render() {
220
+ const weekLabels = ['D', 'L', 'M', 'M', 'J', 'V', 'S'];
221
+ const formattedSelection = this.isRange
222
+ ? `${this.dateString} (${this.startTime} - ${this.endTime})`
223
+ : this.dateString;
224
+
225
+ return html`
226
+ <div class=${classMap({ 'modal-overlay': true, 'open': this.open })}>
227
+ <div class="modal-content">
228
+ <h3>${this.existingRule ? 'Gestionar Bloqueo' : 'Crear Bloqueo'}</h3>
229
+
230
+ <div style="font-size: 0.85rem; color: var(--glatam-text-secondary);">
231
+ Selección: <strong>${formattedSelection}</strong>
232
+ </div>
233
+
234
+ <div class="form-group">
235
+ <label>Descripción / Nota</label>
236
+ <input
237
+ type="text"
238
+ .value=${this.description}
239
+ @input=${(e: any) => this.description = e.target.value}
240
+ placeholder="Ej. Reunión de equipo, Vacaciones"
241
+ />
242
+ </div>
243
+
244
+ <div class="switch-row">
245
+ <label>Bloquear todo el día</label>
246
+ <label class="switch">
247
+ <input
248
+ type="checkbox"
249
+ .checked=${this.blockAllDay}
250
+ @change=${(e: any) => this.blockAllDay = e.target.checked}
251
+ />
252
+ <span class="slider"></span>
253
+ </label>
254
+ </div>
255
+
256
+ <div class="switch-row">
257
+ <label>Repetir semanalmente</label>
258
+ <label class="switch">
259
+ <input
260
+ type="checkbox"
261
+ .checked=${this.isRecurring}
262
+ @change=${(e: any) => this.isRecurring = e.target.checked}
263
+ />
264
+ <span class="slider"></span>
265
+ </label>
266
+ </div>
267
+
268
+ ${this.isRecurring
269
+ ? html`
270
+ <div class="form-group">
271
+ <label>Repetir los días</label>
272
+ <div class="days-grid">
273
+ ${[1, 2, 3, 4, 5, 6, 0].map(day => html`
274
+ <button
275
+ class="day-btn ${this.selectedDays.includes(day) ? 'selected' : ''}"
276
+ @click=${() => this.toggleDay(day)}
277
+ >
278
+ ${weekLabels[day]}
279
+ </button>
280
+ `)}
281
+ </div>
282
+ </div>
283
+ `
284
+ : ''}
285
+
286
+ <div class="btn-actions">
287
+ ${this.existingRule
288
+ ? html`<button class="btn btn-danger" @click=${this.handleDelete}>Eliminar</button>`
289
+ : ''}
290
+ <button class="btn btn-cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>Cancelar</button>
291
+ <button class="btn btn-save" @click=${this.handleSave}>Guardar</button>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ `;
296
+ }
297
+ }
@@ -0,0 +1,147 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import {
4
+ generateMonthDays,
5
+ isTimeBlocked,
6
+ BlockingRule,
7
+ TimeSlot
8
+ } from '@glatam/calendar-core';
9
+ import { variablesStyles } from './styles/variables.css.js';
10
+ import { calendarStyles } from './styles/calendar.css.js';
11
+ import { getTimezoneOffsetDiff, shiftSlot } from './utils/timezone-utils.js';
12
+
13
+ import './views/month-view.js';
14
+
15
+ @customElement('glatam-calendar-mini')
16
+ export class GlatamCalendarMini extends LitElement {
17
+ static styles = [
18
+ variablesStyles,
19
+ calendarStyles,
20
+ css`
21
+ :host {
22
+ display: inline-block;
23
+ background: transparent;
24
+ border: none;
25
+ padding: 0;
26
+ box-shadow: none;
27
+ width: 100%;
28
+ }
29
+ `
30
+ ];
31
+
32
+ @property({ type: String }) role = 'buyer'; // Default to buyer for checkout
33
+ @property({ type: String }) locale = 'es';
34
+ @property({ type: Number }) startOfWeekDay = 0;
35
+ @property({ type: Array }) rules: BlockingRule[] = [];
36
+ @property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
37
+ @property({ type: String, reflect: true }) size = 'medium';
38
+ @property({ type: String }) hostTimezone = 'America/Bogota'; @property({ type: String }) activeTimezone = 'local';
39
+ @property({ type: String }) minDate = ''; @property({ type: String }) maxDate = '';
40
+ @property({ type: Boolean }) showNeighboringMonth = true;
41
+ @property({ attribute: false }) tileClassName: ((data: { date: Date; dateString: string }) => string) | null = null;
42
+ @property({ type: Array }) slots: TimeSlot[] = [
43
+ { start: '09:00', end: '10:00' }, { start: '10:00', end: '11:00' },
44
+ { start: '11:00', end: '12:00' }, { start: '12:00', end: '13:00' },
45
+ { start: '13:00', end: '14:00' }, { start: '14:00', end: '15:00' },
46
+ { start: '15:00', end: '16:00' }, { start: '16:00', end: '17:00' },
47
+ { start: '17:00', end: '18:00' },
48
+ ];
49
+
50
+ @state() private activeDate = new Date();
51
+ @state() private dropdownOpen = false;
52
+ @state() private dropdownSelectedDateString = '';
53
+
54
+ private handleDropdownDaySelect(e: CustomEvent<{ dateString: string }>) {
55
+ this.dropdownSelectedDateString = e.detail.dateString;
56
+ }
57
+
58
+ private selectDropdownSlot(slot: any) {
59
+ const detail = {
60
+ dateString: this.dropdownSelectedDateString,
61
+ start: slot.displayStart,
62
+ end: slot.displayEnd,
63
+ hostStart: slot.start,
64
+ hostEnd: slot.end
65
+ };
66
+ this.selectedRange = { dateString: this.dropdownSelectedDateString, start: slot.displayStart, end: slot.displayEnd };
67
+ this.dispatchEvent(new CustomEvent('booking-selected', { detail, bubbles: true, composed: true }));
68
+ this.dropdownOpen = false;
69
+ this.dropdownSelectedDateString = '';
70
+ }
71
+
72
+ render() {
73
+ const label = this.selectedRange
74
+ ? `Reserva: ${this.selectedRange.dateString} (${this.selectedRange.start} - ${this.selectedRange.end})`
75
+ : 'Seleccionar Fecha y Hora';
76
+
77
+ const y = this.activeDate.getFullYear(), m = this.activeDate.getMonth();
78
+ const dropdownDate = this.dropdownSelectedDateString ? new Date(this.dropdownSelectedDateString + 'T00:00:00') : null;
79
+ const offset = dropdownDate && this.activeTimezone === 'local' ? getTimezoneOffsetDiff(dropdownDate, this.hostTimezone, 'local') : 0;
80
+
81
+ const dropdownSlots = dropdownDate
82
+ ? this.slots.map(slot => {
83
+ const isBlocked = isTimeBlocked(dropdownDate, slot, this.rules);
84
+ const shifted = shiftSlot(slot, offset);
85
+ const suffix = shifted.dayShift > 0 ? ' (+1d)' : shifted.dayShift < 0 ? ' (-1d)' : '';
86
+ return {
87
+ ...slot,
88
+ displayStart: shifted.start + suffix,
89
+ displayEnd: shifted.end + suffix,
90
+ isBlocked
91
+ };
92
+ })
93
+ : [];
94
+
95
+ return html`
96
+ <div class="dropdown-container">
97
+ <button class="btn btn-primary dropdown-toggle" @click=${() => this.dropdownOpen = !this.dropdownOpen}>
98
+ <span>${label}</span> <span>${this.dropdownOpen ? '▲' : '▼'}</span>
99
+ </button>
100
+
101
+ ${this.dropdownOpen
102
+ ? html`
103
+ <div class="dropdown-card" style="--glatam-day-min-height: 38px;">
104
+ ${!this.dropdownSelectedDateString
105
+ ? html`
106
+ <div style="font-weight:600; font-size:0.9rem; text-align:center; color: var(--glatam-text);">Selecciona un Día</div>
107
+ <glatam-calendar-month-view
108
+ .days=${generateMonthDays(y, m, this.rules, this.slots, this.startOfWeekDay)}
109
+ .locale=${this.locale}
110
+ .startOfWeekDay=${this.startOfWeekDay}
111
+ .role=${this.role}
112
+ size="small"
113
+ .minDate=${this.minDate}
114
+ .maxDate=${this.maxDate}
115
+ .showNeighboringMonth=${this.showNeighboringMonth}
116
+ .tileClassName=${this.tileClassName}
117
+ @day-select=${this.handleDropdownDaySelect}
118
+ ></glatam-calendar-month-view>
119
+ `
120
+ : html`
121
+ <div style="display:flex; justify-content:space-between; align-items:center; border-bottom: 1px solid var(--glatam-border); padding-bottom:8px;">
122
+ <button class="btn" style="height:28px; padding:0 8px; font-size:0.75rem;" @click=${() => this.dropdownSelectedDateString = ''}>&lt; Volver</button>
123
+ <span style="font-size:0.8rem; font-weight:600; color: var(--glatam-text);">${this.dropdownSelectedDateString}</span>
124
+ </div>
125
+ <div class="slot-list">
126
+ ${dropdownSlots.map(s => html`
127
+ <button
128
+ class="slot-btn ${s.isBlocked ? 'blocked' : ''}"
129
+ ?disabled=${s.isBlocked}
130
+ @click=${() => this.selectDropdownSlot(s)}
131
+ >
132
+ ${s.displayStart} - ${s.displayEnd} ${s.isBlocked ? '(Ocupado)' : ''}
133
+ </button>
134
+ `)}
135
+ </div>
136
+ `}
137
+ </div>
138
+ `
139
+ : ''}
140
+ </div>
141
+ `;
142
+ }
143
+ }
144
+
145
+ declare global {
146
+ interface HTMLElementTagNameMap { 'glatam-calendar-mini': GlatamCalendarMini; }
147
+ }