@mintplayer/scheduler-wc 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.
- package/package.json +33 -0
- package/src/components/mp-scheduler.d.ts +91 -0
- package/src/components/mp-scheduler.js +982 -0
- package/src/components/mp-scheduler.js.map +1 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +9 -0
- package/src/index.js.map +1 -0
- package/src/state/scheduler-state.d.ts +148 -0
- package/src/state/scheduler-state.js +253 -0
- package/src/state/scheduler-state.js.map +1 -0
- package/src/styles/scheduler.styles.d.ts +5 -0
- package/src/styles/scheduler.styles.js +619 -0
- package/src/styles/scheduler.styles.js.map +1 -0
- package/src/views/base-view.d.ts +37 -0
- package/src/views/base-view.js +40 -0
- package/src/views/base-view.js.map +1 -0
- package/src/views/day-view.d.ts +18 -0
- package/src/views/day-view.js +250 -0
- package/src/views/day-view.js.map +1 -0
- package/src/views/index.d.ts +6 -0
- package/src/views/index.js +7 -0
- package/src/views/index.js.map +1 -0
- package/src/views/month-view.d.ts +14 -0
- package/src/views/month-view.js +141 -0
- package/src/views/month-view.js.map +1 -0
- package/src/views/timeline-view.d.ts +17 -0
- package/src/views/timeline-view.js +223 -0
- package/src/views/timeline-view.js.map +1 -0
- package/src/views/week-view.d.ts +19 -0
- package/src/views/week-view.js +299 -0
- package/src/views/week-view.js.map +1 -0
- package/src/views/year-view.d.ts +11 -0
- package/src/views/year-view.js +83 -0
- package/src/views/year-view.js.map +1 -0
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
import { dateService, generateEventId, } from '@mintplayer/scheduler-core';
|
|
2
|
+
import { SchedulerStateManager } from '../state/scheduler-state';
|
|
3
|
+
import { YearView } from '../views/year-view';
|
|
4
|
+
import { MonthView } from '../views/month-view';
|
|
5
|
+
import { WeekView } from '../views/week-view';
|
|
6
|
+
import { DayView } from '../views/day-view';
|
|
7
|
+
import { TimelineView } from '../views/timeline-view';
|
|
8
|
+
import { schedulerStyles } from '../styles/scheduler.styles';
|
|
9
|
+
/**
|
|
10
|
+
* MpScheduler Web Component
|
|
11
|
+
*
|
|
12
|
+
* A fully-featured scheduler/calendar component
|
|
13
|
+
*/
|
|
14
|
+
export class MpScheduler extends HTMLElement {
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.currentView = null;
|
|
18
|
+
this.contentContainer = null;
|
|
19
|
+
// Track previous state for change detection
|
|
20
|
+
this.previousView = null;
|
|
21
|
+
this.previousDate = null;
|
|
22
|
+
this.previousSelectedEventId = null;
|
|
23
|
+
// Pending drag state (before actual drag starts)
|
|
24
|
+
this.pendingDrag = null;
|
|
25
|
+
this.DRAG_THRESHOLD = 5; // pixels before drag starts
|
|
26
|
+
// Touch state
|
|
27
|
+
this.touchHoldTimer = null;
|
|
28
|
+
this.touchStartPosition = null;
|
|
29
|
+
this.isTouchDragMode = false;
|
|
30
|
+
this.touchHoldTarget = null;
|
|
31
|
+
this.TOUCH_HOLD_DURATION = 500; // ms before touch drag activates
|
|
32
|
+
this.TOUCH_MOVE_THRESHOLD = 10; // pixels of movement before canceling hold
|
|
33
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
34
|
+
this.stateManager = new SchedulerStateManager();
|
|
35
|
+
// Bind event handlers
|
|
36
|
+
this.boundHandleMouseDown = this.handleMouseDown.bind(this);
|
|
37
|
+
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
38
|
+
this.boundHandleMouseUp = this.handleMouseUp.bind(this);
|
|
39
|
+
this.boundHandleClick = this.handleClick.bind(this);
|
|
40
|
+
this.boundHandleDblClick = this.handleDblClick.bind(this);
|
|
41
|
+
this.boundHandleKeyDown = this.handleKeyDown.bind(this);
|
|
42
|
+
this.boundHandleTouchStart = this.handleTouchStart.bind(this);
|
|
43
|
+
this.boundHandleTouchMove = this.handleTouchMove.bind(this);
|
|
44
|
+
this.boundHandleTouchEnd = this.handleTouchEnd.bind(this);
|
|
45
|
+
this.boundHandleTouchCancel = this.handleTouchCancel.bind(this);
|
|
46
|
+
// Subscribe to state changes
|
|
47
|
+
this.stateManager.subscribe((state) => this.onStateChange(state));
|
|
48
|
+
}
|
|
49
|
+
connectedCallback() {
|
|
50
|
+
this.render();
|
|
51
|
+
this.attachEventListeners();
|
|
52
|
+
}
|
|
53
|
+
disconnectedCallback() {
|
|
54
|
+
var _a;
|
|
55
|
+
this.detachEventListeners();
|
|
56
|
+
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
57
|
+
}
|
|
58
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
59
|
+
if (oldValue === newValue)
|
|
60
|
+
return;
|
|
61
|
+
switch (name) {
|
|
62
|
+
case 'view':
|
|
63
|
+
if (newValue && ['year', 'month', 'week', 'day', 'timeline'].includes(newValue)) {
|
|
64
|
+
this.stateManager.setView(newValue);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case 'date':
|
|
68
|
+
if (newValue) {
|
|
69
|
+
this.stateManager.setDate(new Date(newValue));
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case 'locale':
|
|
73
|
+
if (newValue) {
|
|
74
|
+
this.stateManager.setOptions({ locale: newValue });
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'first-day-of-week':
|
|
78
|
+
if (newValue) {
|
|
79
|
+
const day = parseInt(newValue, 10);
|
|
80
|
+
if (day >= 0 && day <= 6) {
|
|
81
|
+
this.stateManager.setOptions({ firstDayOfWeek: day });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
case 'slot-duration':
|
|
86
|
+
if (newValue) {
|
|
87
|
+
this.stateManager.setOptions({ slotDuration: parseInt(newValue, 10) });
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case 'time-format':
|
|
91
|
+
if (newValue && (newValue === '12h' || newValue === '24h')) {
|
|
92
|
+
this.stateManager.setOptions({ timeFormat: newValue });
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case 'editable':
|
|
96
|
+
this.stateManager.setOptions({ editable: newValue !== 'false' });
|
|
97
|
+
break;
|
|
98
|
+
case 'selectable':
|
|
99
|
+
this.stateManager.setOptions({ selectable: newValue !== 'false' });
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Public API
|
|
104
|
+
get view() {
|
|
105
|
+
return this.stateManager.getState().view;
|
|
106
|
+
}
|
|
107
|
+
set view(value) {
|
|
108
|
+
this.stateManager.setView(value);
|
|
109
|
+
}
|
|
110
|
+
get date() {
|
|
111
|
+
return this.stateManager.getState().date;
|
|
112
|
+
}
|
|
113
|
+
set date(value) {
|
|
114
|
+
this.stateManager.setDate(value);
|
|
115
|
+
}
|
|
116
|
+
get events() {
|
|
117
|
+
return this.stateManager.getState().events;
|
|
118
|
+
}
|
|
119
|
+
set events(value) {
|
|
120
|
+
this.stateManager.setEvents(value);
|
|
121
|
+
}
|
|
122
|
+
get resources() {
|
|
123
|
+
return this.stateManager.getState().resources;
|
|
124
|
+
}
|
|
125
|
+
set resources(value) {
|
|
126
|
+
this.stateManager.setResources(value);
|
|
127
|
+
}
|
|
128
|
+
get options() {
|
|
129
|
+
return this.stateManager.getState().options;
|
|
130
|
+
}
|
|
131
|
+
set options(value) {
|
|
132
|
+
this.stateManager.setOptions(value);
|
|
133
|
+
}
|
|
134
|
+
get selectedEvent() {
|
|
135
|
+
return this.stateManager.getState().selectedEvent;
|
|
136
|
+
}
|
|
137
|
+
set selectedEvent(value) {
|
|
138
|
+
this.stateManager.setSelectedEvent(value);
|
|
139
|
+
}
|
|
140
|
+
get selectedRange() {
|
|
141
|
+
const state = this.stateManager.getState();
|
|
142
|
+
if (state.previewEvent) {
|
|
143
|
+
return { start: state.previewEvent.start, end: state.previewEvent.end };
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
next() {
|
|
148
|
+
this.stateManager.next();
|
|
149
|
+
}
|
|
150
|
+
prev() {
|
|
151
|
+
this.stateManager.prev();
|
|
152
|
+
}
|
|
153
|
+
today() {
|
|
154
|
+
this.stateManager.today();
|
|
155
|
+
}
|
|
156
|
+
gotoDate(date) {
|
|
157
|
+
this.stateManager.gotoDate(date);
|
|
158
|
+
}
|
|
159
|
+
changeView(view) {
|
|
160
|
+
this.stateManager.setView(view);
|
|
161
|
+
}
|
|
162
|
+
addEvent(event) {
|
|
163
|
+
this.stateManager.addEvent(event);
|
|
164
|
+
}
|
|
165
|
+
updateEvent(event) {
|
|
166
|
+
this.stateManager.updateEvent(event);
|
|
167
|
+
}
|
|
168
|
+
removeEvent(eventId) {
|
|
169
|
+
this.stateManager.removeEvent(eventId);
|
|
170
|
+
}
|
|
171
|
+
getEventById(eventId) {
|
|
172
|
+
var _a;
|
|
173
|
+
return (_a = this.events.find((e) => e.id === eventId)) !== null && _a !== void 0 ? _a : null;
|
|
174
|
+
}
|
|
175
|
+
refetchEvents() {
|
|
176
|
+
var _a;
|
|
177
|
+
// Trigger re-render
|
|
178
|
+
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.update(this.stateManager.getState());
|
|
179
|
+
}
|
|
180
|
+
// Private methods
|
|
181
|
+
render() {
|
|
182
|
+
// Add styles
|
|
183
|
+
const style = document.createElement('style');
|
|
184
|
+
style.textContent = schedulerStyles;
|
|
185
|
+
this.shadow.appendChild(style);
|
|
186
|
+
// Create container
|
|
187
|
+
const container = document.createElement('div');
|
|
188
|
+
container.className = 'scheduler-container';
|
|
189
|
+
// Header
|
|
190
|
+
const header = this.createHeader();
|
|
191
|
+
container.appendChild(header);
|
|
192
|
+
// Content
|
|
193
|
+
this.contentContainer = document.createElement('div');
|
|
194
|
+
this.contentContainer.className = 'scheduler-content';
|
|
195
|
+
container.appendChild(this.contentContainer);
|
|
196
|
+
this.shadow.appendChild(container);
|
|
197
|
+
// Initial view render
|
|
198
|
+
this.renderView();
|
|
199
|
+
}
|
|
200
|
+
createHeader() {
|
|
201
|
+
const header = document.createElement('header');
|
|
202
|
+
header.className = 'scheduler-header';
|
|
203
|
+
// Navigation
|
|
204
|
+
const nav = document.createElement('nav');
|
|
205
|
+
nav.className = 'scheduler-nav';
|
|
206
|
+
const prevBtn = document.createElement('button');
|
|
207
|
+
prevBtn.textContent = '‹';
|
|
208
|
+
prevBtn.title = 'Previous';
|
|
209
|
+
prevBtn.addEventListener('click', () => this.prev());
|
|
210
|
+
const nextBtn = document.createElement('button');
|
|
211
|
+
nextBtn.textContent = '›';
|
|
212
|
+
nextBtn.title = 'Next';
|
|
213
|
+
nextBtn.addEventListener('click', () => this.next());
|
|
214
|
+
const todayBtn = document.createElement('button');
|
|
215
|
+
todayBtn.textContent = 'Today';
|
|
216
|
+
todayBtn.addEventListener('click', () => this.today());
|
|
217
|
+
nav.appendChild(prevBtn);
|
|
218
|
+
nav.appendChild(nextBtn);
|
|
219
|
+
nav.appendChild(todayBtn);
|
|
220
|
+
// Title
|
|
221
|
+
const title = document.createElement('div');
|
|
222
|
+
title.className = 'scheduler-title';
|
|
223
|
+
this.updateTitle(title);
|
|
224
|
+
// View switcher
|
|
225
|
+
const viewSwitcher = document.createElement('div');
|
|
226
|
+
viewSwitcher.className = 'scheduler-view-switcher';
|
|
227
|
+
const views = [
|
|
228
|
+
{ key: 'year', label: 'Year' },
|
|
229
|
+
{ key: 'month', label: 'Month' },
|
|
230
|
+
{ key: 'week', label: 'Week' },
|
|
231
|
+
{ key: 'day', label: 'Day' },
|
|
232
|
+
{ key: 'timeline', label: 'Timeline' },
|
|
233
|
+
];
|
|
234
|
+
for (const { key, label } of views) {
|
|
235
|
+
const btn = document.createElement('button');
|
|
236
|
+
btn.textContent = label;
|
|
237
|
+
btn.dataset['view'] = key;
|
|
238
|
+
if (key === this.view) {
|
|
239
|
+
btn.classList.add('active');
|
|
240
|
+
}
|
|
241
|
+
btn.addEventListener('click', () => this.changeView(key));
|
|
242
|
+
viewSwitcher.appendChild(btn);
|
|
243
|
+
}
|
|
244
|
+
header.appendChild(nav);
|
|
245
|
+
header.appendChild(title);
|
|
246
|
+
header.appendChild(viewSwitcher);
|
|
247
|
+
return header;
|
|
248
|
+
}
|
|
249
|
+
updateTitle(titleEl) {
|
|
250
|
+
const title = titleEl !== null && titleEl !== void 0 ? titleEl : this.shadow.querySelector('.scheduler-title');
|
|
251
|
+
if (!title)
|
|
252
|
+
return;
|
|
253
|
+
const state = this.stateManager.getState();
|
|
254
|
+
const { date, view, options } = state;
|
|
255
|
+
let titleText = '';
|
|
256
|
+
switch (view) {
|
|
257
|
+
case 'year':
|
|
258
|
+
titleText = date.getFullYear().toString();
|
|
259
|
+
break;
|
|
260
|
+
case 'month':
|
|
261
|
+
titleText = dateService.formatDate(date, options.locale, {
|
|
262
|
+
month: 'long',
|
|
263
|
+
year: 'numeric',
|
|
264
|
+
});
|
|
265
|
+
break;
|
|
266
|
+
case 'week':
|
|
267
|
+
case 'timeline': {
|
|
268
|
+
const weekStart = dateService.getWeekStart(date, options.firstDayOfWeek);
|
|
269
|
+
const weekEnd = dateService.addDays(weekStart, 6);
|
|
270
|
+
titleText = `${dateService.formatDate(weekStart, options.locale, { month: 'short', day: 'numeric' })} - ${dateService.formatDate(weekEnd, options.locale, { month: 'short', day: 'numeric', year: 'numeric' })}`;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'day':
|
|
274
|
+
titleText = dateService.formatDate(date, options.locale, {
|
|
275
|
+
weekday: 'long',
|
|
276
|
+
month: 'long',
|
|
277
|
+
day: 'numeric',
|
|
278
|
+
year: 'numeric',
|
|
279
|
+
});
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
title.textContent = titleText;
|
|
283
|
+
}
|
|
284
|
+
renderView() {
|
|
285
|
+
var _a, _b;
|
|
286
|
+
if (!this.contentContainer)
|
|
287
|
+
return;
|
|
288
|
+
// Destroy previous view
|
|
289
|
+
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
290
|
+
const state = this.stateManager.getState();
|
|
291
|
+
// Create new view
|
|
292
|
+
switch (state.view) {
|
|
293
|
+
case 'year':
|
|
294
|
+
this.currentView = new YearView(this.contentContainer, state);
|
|
295
|
+
break;
|
|
296
|
+
case 'month':
|
|
297
|
+
this.currentView = new MonthView(this.contentContainer, state);
|
|
298
|
+
break;
|
|
299
|
+
case 'week':
|
|
300
|
+
this.currentView = new WeekView(this.contentContainer, state);
|
|
301
|
+
break;
|
|
302
|
+
case 'day':
|
|
303
|
+
this.currentView = new DayView(this.contentContainer, state);
|
|
304
|
+
break;
|
|
305
|
+
case 'timeline':
|
|
306
|
+
this.currentView = new TimelineView(this.contentContainer, state);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
(_b = this.currentView) === null || _b === void 0 ? void 0 : _b.render();
|
|
310
|
+
}
|
|
311
|
+
onStateChange(state) {
|
|
312
|
+
var _a, _b;
|
|
313
|
+
// Detect view/date changes and dispatch events
|
|
314
|
+
const viewChanged = this.previousView !== null && this.previousView !== state.view;
|
|
315
|
+
const dateChanged = this.previousDate !== null && this.previousDate.getTime() !== state.date.getTime();
|
|
316
|
+
const selectedEventId = (_b = (_a = state.selectedEvent) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
317
|
+
const selectionChanged = this.previousSelectedEventId !== null && this.previousSelectedEventId !== selectedEventId;
|
|
318
|
+
// Dispatch view-change event if view or date changed (but not on initial render)
|
|
319
|
+
if (viewChanged || dateChanged) {
|
|
320
|
+
this.dispatchEvent(new CustomEvent('view-change', {
|
|
321
|
+
detail: { view: state.view, date: state.date },
|
|
322
|
+
bubbles: true,
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
// Dispatch selection-change event if selected event changed (but not on initial render)
|
|
326
|
+
if (selectionChanged) {
|
|
327
|
+
this.dispatchEvent(new CustomEvent('selection-change', {
|
|
328
|
+
detail: { selectedEvent: state.selectedEvent },
|
|
329
|
+
bubbles: true,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
// Update previous state tracking
|
|
333
|
+
this.previousView = state.view;
|
|
334
|
+
this.previousDate = new Date(state.date);
|
|
335
|
+
this.previousSelectedEventId = selectedEventId;
|
|
336
|
+
// Update title
|
|
337
|
+
this.updateTitle();
|
|
338
|
+
// Update view switcher active state
|
|
339
|
+
const buttons = this.shadow.querySelectorAll('.scheduler-view-switcher button');
|
|
340
|
+
buttons.forEach((btn) => {
|
|
341
|
+
const btnEl = btn;
|
|
342
|
+
btnEl.classList.toggle('active', btnEl.dataset['view'] === state.view);
|
|
343
|
+
});
|
|
344
|
+
// Update or re-render view
|
|
345
|
+
if (this.currentView) {
|
|
346
|
+
// Check if view type changed
|
|
347
|
+
const viewTypeChanged = this.viewTypeChanged(state.view);
|
|
348
|
+
if (viewTypeChanged) {
|
|
349
|
+
this.renderView();
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
this.currentView.update(state);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
viewTypeChanged(newView) {
|
|
357
|
+
if (!this.currentView)
|
|
358
|
+
return true;
|
|
359
|
+
const viewClass = this.currentView.constructor.name.toLowerCase();
|
|
360
|
+
return !viewClass.includes(newView);
|
|
361
|
+
}
|
|
362
|
+
attachEventListeners() {
|
|
363
|
+
const root = this.shadow;
|
|
364
|
+
root.addEventListener('mousedown', this.boundHandleMouseDown);
|
|
365
|
+
document.addEventListener('mousemove', this.boundHandleMouseMove);
|
|
366
|
+
document.addEventListener('mouseup', this.boundHandleMouseUp);
|
|
367
|
+
root.addEventListener('click', this.boundHandleClick);
|
|
368
|
+
root.addEventListener('dblclick', this.boundHandleDblClick);
|
|
369
|
+
this.addEventListener('keydown', this.boundHandleKeyDown);
|
|
370
|
+
// Touch events
|
|
371
|
+
root.addEventListener('touchstart', this.boundHandleTouchStart, { passive: false });
|
|
372
|
+
root.addEventListener('touchmove', this.boundHandleTouchMove, { passive: false });
|
|
373
|
+
root.addEventListener('touchend', this.boundHandleTouchEnd);
|
|
374
|
+
root.addEventListener('touchcancel', this.boundHandleTouchCancel);
|
|
375
|
+
}
|
|
376
|
+
detachEventListeners() {
|
|
377
|
+
const root = this.shadow;
|
|
378
|
+
root.removeEventListener('mousedown', this.boundHandleMouseDown);
|
|
379
|
+
document.removeEventListener('mousemove', this.boundHandleMouseMove);
|
|
380
|
+
document.removeEventListener('mouseup', this.boundHandleMouseUp);
|
|
381
|
+
root.removeEventListener('click', this.boundHandleClick);
|
|
382
|
+
root.removeEventListener('dblclick', this.boundHandleDblClick);
|
|
383
|
+
this.removeEventListener('keydown', this.boundHandleKeyDown);
|
|
384
|
+
// Touch events
|
|
385
|
+
root.removeEventListener('touchstart', this.boundHandleTouchStart);
|
|
386
|
+
root.removeEventListener('touchmove', this.boundHandleTouchMove);
|
|
387
|
+
root.removeEventListener('touchend', this.boundHandleTouchEnd);
|
|
388
|
+
root.removeEventListener('touchcancel', this.boundHandleTouchCancel);
|
|
389
|
+
this.cancelTouchHold();
|
|
390
|
+
}
|
|
391
|
+
handleMouseDown(e) {
|
|
392
|
+
const state = this.stateManager.getState();
|
|
393
|
+
if (!state.options.editable)
|
|
394
|
+
return;
|
|
395
|
+
const target = e.target;
|
|
396
|
+
// Check for resize handle - start drag immediately (no click behavior)
|
|
397
|
+
const resizeHandle = target.closest('.resize-handle');
|
|
398
|
+
if (resizeHandle) {
|
|
399
|
+
const eventEl = resizeHandle.closest('.scheduler-event');
|
|
400
|
+
const eventId = eventEl === null || eventEl === void 0 ? void 0 : eventEl.dataset['eventId'];
|
|
401
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
402
|
+
if (event) {
|
|
403
|
+
const handleType = resizeHandle.dataset['handle'];
|
|
404
|
+
this.pendingDrag = {
|
|
405
|
+
type: ('resize-' + handleType),
|
|
406
|
+
event,
|
|
407
|
+
startX: e.clientX,
|
|
408
|
+
startY: e.clientY,
|
|
409
|
+
};
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Check for event - set up pending drag (actual drag starts on mouse move)
|
|
415
|
+
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
416
|
+
if (eventEl && !eventEl.classList.contains('preview')) {
|
|
417
|
+
const eventId = eventEl.dataset['eventId'];
|
|
418
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
419
|
+
if (event && event.draggable !== false) {
|
|
420
|
+
this.pendingDrag = {
|
|
421
|
+
type: 'move',
|
|
422
|
+
event,
|
|
423
|
+
startX: e.clientX,
|
|
424
|
+
startY: e.clientY,
|
|
425
|
+
};
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Check for slot - set up pending drag for create
|
|
431
|
+
const slotEl = target.closest('.scheduler-time-slot, .scheduler-timeline-slot');
|
|
432
|
+
if (slotEl && state.options.selectable) {
|
|
433
|
+
this.pendingDrag = {
|
|
434
|
+
type: 'create',
|
|
435
|
+
event: null,
|
|
436
|
+
startX: e.clientX,
|
|
437
|
+
startY: e.clientY,
|
|
438
|
+
slotEl,
|
|
439
|
+
};
|
|
440
|
+
e.preventDefault();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
handleMouseMove(e) {
|
|
444
|
+
// Check if we have a pending drag that should start
|
|
445
|
+
if (this.pendingDrag) {
|
|
446
|
+
const dx = e.clientX - this.pendingDrag.startX;
|
|
447
|
+
const dy = e.clientY - this.pendingDrag.startY;
|
|
448
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
449
|
+
if (distance >= this.DRAG_THRESHOLD) {
|
|
450
|
+
// Start the actual drag
|
|
451
|
+
this.startDrag(this.pendingDrag.type, this.pendingDrag.event, e, this.pendingDrag.slotEl);
|
|
452
|
+
this.pendingDrag = null;
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const state = this.stateManager.getState();
|
|
457
|
+
if (!state.dragState)
|
|
458
|
+
return;
|
|
459
|
+
const slot = this.getSlotAtPosition(e.clientX, e.clientY);
|
|
460
|
+
if (!slot)
|
|
461
|
+
return;
|
|
462
|
+
const preview = this.calculatePreview(state.dragState.type, state.dragState, slot);
|
|
463
|
+
if (preview) {
|
|
464
|
+
this.stateManager.updateDrag(slot, preview);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
handleMouseUp(e) {
|
|
468
|
+
// If we had a pending drag that never started, it's a click
|
|
469
|
+
if (this.pendingDrag) {
|
|
470
|
+
const { type, event } = this.pendingDrag;
|
|
471
|
+
this.pendingDrag = null;
|
|
472
|
+
// If it was on an event, select it
|
|
473
|
+
if (type === 'move' && event) {
|
|
474
|
+
this.stateManager.setSelectedEvent(event);
|
|
475
|
+
this.dispatchEvent(new CustomEvent('event-click', {
|
|
476
|
+
detail: { event, originalEvent: e },
|
|
477
|
+
bubbles: true,
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const state = this.stateManager.getState();
|
|
483
|
+
if (!state.dragState)
|
|
484
|
+
return;
|
|
485
|
+
const { type, event, preview } = state.dragState;
|
|
486
|
+
// Finalize the drag operation
|
|
487
|
+
if (preview) {
|
|
488
|
+
if (type === 'create') {
|
|
489
|
+
// Create new event
|
|
490
|
+
const newEvent = {
|
|
491
|
+
id: generateEventId(),
|
|
492
|
+
title: 'New Event',
|
|
493
|
+
start: preview.start,
|
|
494
|
+
end: preview.end,
|
|
495
|
+
color: '#3788d8',
|
|
496
|
+
};
|
|
497
|
+
this.stateManager.addEvent(newEvent);
|
|
498
|
+
this.dispatchEvent(new CustomEvent('event-create', {
|
|
499
|
+
detail: { event: newEvent, originalEvent: e },
|
|
500
|
+
bubbles: true,
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
else if (type === 'move' && event) {
|
|
504
|
+
// Move event
|
|
505
|
+
const oldEvent = Object.assign({}, event);
|
|
506
|
+
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
507
|
+
this.stateManager.updateEvent(updatedEvent);
|
|
508
|
+
this.dispatchEvent(new CustomEvent('event-update', {
|
|
509
|
+
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
510
|
+
bubbles: true,
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
else if ((type === 'resize-start' || type === 'resize-end') && event) {
|
|
514
|
+
// Resize event
|
|
515
|
+
const oldEvent = Object.assign({}, event);
|
|
516
|
+
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
517
|
+
this.stateManager.updateEvent(updatedEvent);
|
|
518
|
+
this.dispatchEvent(new CustomEvent('event-update', {
|
|
519
|
+
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
520
|
+
bubbles: true,
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
this.stateManager.endDrag();
|
|
525
|
+
}
|
|
526
|
+
handleClick(e) {
|
|
527
|
+
const target = e.target;
|
|
528
|
+
// Group toggle
|
|
529
|
+
const toggle = target.closest('.expand-toggle');
|
|
530
|
+
if (toggle) {
|
|
531
|
+
const groupId = toggle.dataset['groupId'];
|
|
532
|
+
if (groupId) {
|
|
533
|
+
this.stateManager.toggleGroupCollapse(groupId);
|
|
534
|
+
this.renderView();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Event clicks are handled in handleMouseUp (via pendingDrag)
|
|
539
|
+
// Skip event click handling here to avoid duplicates
|
|
540
|
+
const eventEl = target.closest('.scheduler-event');
|
|
541
|
+
if (eventEl) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
// Date click
|
|
545
|
+
const dayEl = target.closest('[data-date]');
|
|
546
|
+
if (dayEl) {
|
|
547
|
+
const dateStr = dayEl.dataset['date'];
|
|
548
|
+
if (dateStr) {
|
|
549
|
+
this.dispatchEvent(new CustomEvent('date-click', {
|
|
550
|
+
detail: { date: new Date(dateStr), originalEvent: e },
|
|
551
|
+
bubbles: true,
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Month click in year view
|
|
556
|
+
const monthHeader = target.closest('.scheduler-year-month-header');
|
|
557
|
+
if (monthHeader) {
|
|
558
|
+
const monthStr = monthHeader.dataset['month'];
|
|
559
|
+
if (monthStr) {
|
|
560
|
+
this.stateManager.setDate(new Date(monthStr));
|
|
561
|
+
this.stateManager.setView('month');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// More link click
|
|
565
|
+
const moreLink = target.closest('.scheduler-more-link');
|
|
566
|
+
if (moreLink) {
|
|
567
|
+
const dateStr = moreLink.dataset['date'];
|
|
568
|
+
if (dateStr) {
|
|
569
|
+
this.stateManager.setDate(new Date(dateStr));
|
|
570
|
+
this.stateManager.setView('day');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
handleDblClick(e) {
|
|
575
|
+
const target = e.target;
|
|
576
|
+
const eventEl = target.closest('.scheduler-event');
|
|
577
|
+
if (eventEl) {
|
|
578
|
+
const eventId = eventEl.dataset['eventId'];
|
|
579
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
580
|
+
if (event) {
|
|
581
|
+
this.dispatchEvent(new CustomEvent('event-dblclick', {
|
|
582
|
+
detail: { event, originalEvent: e },
|
|
583
|
+
bubbles: true,
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
handleKeyDown(e) {
|
|
589
|
+
const state = this.stateManager.getState();
|
|
590
|
+
switch (e.key) {
|
|
591
|
+
case 'ArrowLeft':
|
|
592
|
+
this.prev();
|
|
593
|
+
e.preventDefault();
|
|
594
|
+
break;
|
|
595
|
+
case 'ArrowRight':
|
|
596
|
+
this.next();
|
|
597
|
+
e.preventDefault();
|
|
598
|
+
break;
|
|
599
|
+
case 't':
|
|
600
|
+
case 'T':
|
|
601
|
+
this.today();
|
|
602
|
+
e.preventDefault();
|
|
603
|
+
break;
|
|
604
|
+
case 'y':
|
|
605
|
+
case 'Y':
|
|
606
|
+
this.changeView('year');
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
break;
|
|
609
|
+
case 'm':
|
|
610
|
+
case 'M':
|
|
611
|
+
this.changeView('month');
|
|
612
|
+
e.preventDefault();
|
|
613
|
+
break;
|
|
614
|
+
case 'w':
|
|
615
|
+
case 'W':
|
|
616
|
+
this.changeView('week');
|
|
617
|
+
e.preventDefault();
|
|
618
|
+
break;
|
|
619
|
+
case 'd':
|
|
620
|
+
case 'D':
|
|
621
|
+
this.changeView('day');
|
|
622
|
+
e.preventDefault();
|
|
623
|
+
break;
|
|
624
|
+
case 'Delete':
|
|
625
|
+
case 'Backspace':
|
|
626
|
+
if (state.selectedEvent) {
|
|
627
|
+
this.dispatchEvent(new CustomEvent('event-delete', {
|
|
628
|
+
detail: { event: state.selectedEvent },
|
|
629
|
+
bubbles: true,
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
case 'Escape':
|
|
634
|
+
if (state.dragState) {
|
|
635
|
+
this.stateManager.endDrag();
|
|
636
|
+
}
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
startDrag(type, event, mouseEvent, slotEl) {
|
|
641
|
+
const slot = slotEl
|
|
642
|
+
? this.getSlotFromElement(slotEl)
|
|
643
|
+
: this.getSlotAtPosition(mouseEvent.clientX, mouseEvent.clientY);
|
|
644
|
+
if (!slot)
|
|
645
|
+
return;
|
|
646
|
+
let preview;
|
|
647
|
+
if (type === 'create') {
|
|
648
|
+
preview = {
|
|
649
|
+
start: slot.start,
|
|
650
|
+
end: slot.end,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
else if (event) {
|
|
654
|
+
preview = {
|
|
655
|
+
start: event.start,
|
|
656
|
+
end: event.end,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
this.stateManager.startDrag({
|
|
663
|
+
type,
|
|
664
|
+
event,
|
|
665
|
+
startSlot: slot,
|
|
666
|
+
currentSlot: slot,
|
|
667
|
+
preview,
|
|
668
|
+
originalEvent: event ? Object.assign({}, event) : undefined,
|
|
669
|
+
meta: type.startsWith('resize-')
|
|
670
|
+
? { resizeHandle: type.replace('resize-', '') }
|
|
671
|
+
: undefined,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
calculatePreview(type, dragState, currentSlot) {
|
|
675
|
+
const { startSlot, event, originalEvent } = dragState;
|
|
676
|
+
if (type === 'create') {
|
|
677
|
+
// Extend selection from start slot to current slot
|
|
678
|
+
const start = new Date(Math.min(startSlot.start.getTime(), currentSlot.start.getTime()));
|
|
679
|
+
const end = new Date(Math.max(startSlot.end.getTime(), currentSlot.end.getTime()));
|
|
680
|
+
return { start, end };
|
|
681
|
+
}
|
|
682
|
+
if (type === 'move' && originalEvent) {
|
|
683
|
+
// Calculate offset and apply to event
|
|
684
|
+
const offsetMs = currentSlot.start.getTime() - startSlot.start.getTime();
|
|
685
|
+
const duration = originalEvent.end.getTime() - originalEvent.start.getTime();
|
|
686
|
+
const newStart = new Date(originalEvent.start.getTime() + offsetMs);
|
|
687
|
+
const newEnd = new Date(newStart.getTime() + duration);
|
|
688
|
+
return { start: newStart, end: newEnd };
|
|
689
|
+
}
|
|
690
|
+
if (type === 'resize-start' && originalEvent) {
|
|
691
|
+
// Move start, keep end fixed
|
|
692
|
+
const newStart = new Date(Math.min(currentSlot.start.getTime(), originalEvent.end.getTime() - 1800000));
|
|
693
|
+
return { start: newStart, end: originalEvent.end };
|
|
694
|
+
}
|
|
695
|
+
if (type === 'resize-end' && originalEvent) {
|
|
696
|
+
// Move end, keep start fixed
|
|
697
|
+
const newEnd = new Date(Math.max(currentSlot.end.getTime(), originalEvent.start.getTime() + 1800000));
|
|
698
|
+
return { start: originalEvent.start, end: newEnd };
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
getSlotAtPosition(clientX, clientY) {
|
|
703
|
+
const slotEl = this.shadow.elementsFromPoint(clientX, clientY)
|
|
704
|
+
.find((el) => el.matches('.scheduler-time-slot, .scheduler-timeline-slot'));
|
|
705
|
+
return slotEl ? this.getSlotFromElement(slotEl) : null;
|
|
706
|
+
}
|
|
707
|
+
getSlotFromElement(el) {
|
|
708
|
+
const startStr = el.dataset['start'];
|
|
709
|
+
const endStr = el.dataset['end'];
|
|
710
|
+
if (!startStr || !endStr)
|
|
711
|
+
return null;
|
|
712
|
+
return {
|
|
713
|
+
start: new Date(startStr),
|
|
714
|
+
end: new Date(endStr),
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
// Touch event handlers
|
|
718
|
+
handleTouchStart(e) {
|
|
719
|
+
const state = this.stateManager.getState();
|
|
720
|
+
if (!state.options.editable)
|
|
721
|
+
return;
|
|
722
|
+
// Only handle single touch
|
|
723
|
+
if (e.touches.length !== 1) {
|
|
724
|
+
this.cancelTouchHold();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const touch = e.touches[0];
|
|
728
|
+
const target = touch.target;
|
|
729
|
+
this.touchStartPosition = { x: touch.clientX, y: touch.clientY };
|
|
730
|
+
// Check if touching an event or slot that could be dragged
|
|
731
|
+
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
732
|
+
const resizeHandle = target.closest('.resize-handle');
|
|
733
|
+
const slotEl = target.closest('.scheduler-time-slot, .scheduler-timeline-slot');
|
|
734
|
+
if (!eventEl && !slotEl) {
|
|
735
|
+
// Not touching a draggable element
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// Store the target for the hold callback
|
|
739
|
+
this.touchHoldTarget = eventEl || slotEl;
|
|
740
|
+
// Add visual feedback class
|
|
741
|
+
if (eventEl) {
|
|
742
|
+
eventEl.classList.add('touch-hold-pending');
|
|
743
|
+
}
|
|
744
|
+
else if (slotEl) {
|
|
745
|
+
slotEl.classList.add('touch-hold-pending');
|
|
746
|
+
}
|
|
747
|
+
// Start the hold timer
|
|
748
|
+
this.touchHoldTimer = setTimeout(() => {
|
|
749
|
+
this.activateTouchDragMode(touch, resizeHandle);
|
|
750
|
+
}, this.TOUCH_HOLD_DURATION);
|
|
751
|
+
}
|
|
752
|
+
activateTouchDragMode(touch, resizeHandle) {
|
|
753
|
+
const state = this.stateManager.getState();
|
|
754
|
+
const target = this.touchHoldTarget;
|
|
755
|
+
if (!target)
|
|
756
|
+
return;
|
|
757
|
+
// Trigger haptic feedback if available
|
|
758
|
+
this.triggerHapticFeedback();
|
|
759
|
+
// Enter touch drag mode
|
|
760
|
+
this.isTouchDragMode = true;
|
|
761
|
+
// Add visual feedback
|
|
762
|
+
const container = this.shadow.querySelector('.scheduler-container');
|
|
763
|
+
container === null || container === void 0 ? void 0 : container.classList.add('touch-drag-mode');
|
|
764
|
+
// Remove pending class, add active class
|
|
765
|
+
target.classList.remove('touch-hold-pending');
|
|
766
|
+
target.classList.add('touch-hold-active');
|
|
767
|
+
// Determine the drag type and start the drag
|
|
768
|
+
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
769
|
+
if (resizeHandle) {
|
|
770
|
+
// Resize operation
|
|
771
|
+
const parentEventEl = resizeHandle.closest('.scheduler-event');
|
|
772
|
+
const eventId = parentEventEl === null || parentEventEl === void 0 ? void 0 : parentEventEl.dataset['eventId'];
|
|
773
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
774
|
+
if (event) {
|
|
775
|
+
const handleType = resizeHandle.dataset['handle'];
|
|
776
|
+
this.startDragFromTouch(('resize-' + handleType), event, touch.clientX, touch.clientY);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
else if (eventEl) {
|
|
780
|
+
// Move operation
|
|
781
|
+
const eventId = eventEl.dataset['eventId'];
|
|
782
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
783
|
+
if (event && event.draggable !== false) {
|
|
784
|
+
this.startDragFromTouch('move', event, touch.clientX, touch.clientY);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
else if (target.matches('.scheduler-time-slot, .scheduler-timeline-slot') && state.options.selectable) {
|
|
788
|
+
// Create operation
|
|
789
|
+
this.startDragFromTouch('create', null, touch.clientX, touch.clientY, target);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
handleTouchMove(e) {
|
|
793
|
+
if (e.touches.length !== 1) {
|
|
794
|
+
this.cancelTouchHold();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const touch = e.touches[0];
|
|
798
|
+
// If we have a pending touch hold, check if user moved too much
|
|
799
|
+
if (this.touchHoldTimer && this.touchStartPosition) {
|
|
800
|
+
const dx = touch.clientX - this.touchStartPosition.x;
|
|
801
|
+
const dy = touch.clientY - this.touchStartPosition.y;
|
|
802
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
803
|
+
if (distance > this.TOUCH_MOVE_THRESHOLD) {
|
|
804
|
+
// User moved too much, cancel the hold and allow normal scrolling
|
|
805
|
+
this.cancelTouchHold();
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
// Still waiting for hold, prevent default to avoid scroll during hold detection
|
|
809
|
+
// But only if we're close to the threshold
|
|
810
|
+
if (distance < this.TOUCH_MOVE_THRESHOLD / 2) {
|
|
811
|
+
// Don't prevent default yet to allow small movements
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
// If in touch drag mode, handle the drag
|
|
816
|
+
if (this.isTouchDragMode) {
|
|
817
|
+
e.preventDefault(); // Prevent scrolling while dragging
|
|
818
|
+
const state = this.stateManager.getState();
|
|
819
|
+
if (!state.dragState)
|
|
820
|
+
return;
|
|
821
|
+
const slot = this.getSlotAtPosition(touch.clientX, touch.clientY);
|
|
822
|
+
if (!slot)
|
|
823
|
+
return;
|
|
824
|
+
const preview = this.calculatePreview(state.dragState.type, state.dragState, slot);
|
|
825
|
+
if (preview) {
|
|
826
|
+
this.stateManager.updateDrag(slot, preview);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
handleTouchEnd(e) {
|
|
831
|
+
// If we had a pending hold that never activated, treat as a tap
|
|
832
|
+
if (this.touchHoldTimer) {
|
|
833
|
+
this.cancelTouchHold();
|
|
834
|
+
// Handle as a tap/click on the target
|
|
835
|
+
if (this.touchHoldTarget) {
|
|
836
|
+
const eventEl = this.touchHoldTarget.closest('.scheduler-event:not(.preview)');
|
|
837
|
+
if (eventEl) {
|
|
838
|
+
const eventId = eventEl.dataset['eventId'];
|
|
839
|
+
const event = eventId ? this.getEventById(eventId) : null;
|
|
840
|
+
if (event) {
|
|
841
|
+
this.stateManager.setSelectedEvent(event);
|
|
842
|
+
this.dispatchEvent(new CustomEvent('event-click', {
|
|
843
|
+
detail: { event, originalEvent: e },
|
|
844
|
+
bubbles: true,
|
|
845
|
+
}));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
this.touchHoldTarget = null;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
// If in touch drag mode, finalize the drag
|
|
853
|
+
if (this.isTouchDragMode) {
|
|
854
|
+
const state = this.stateManager.getState();
|
|
855
|
+
if (state.dragState) {
|
|
856
|
+
const { type, event, preview } = state.dragState;
|
|
857
|
+
if (preview) {
|
|
858
|
+
if (type === 'create') {
|
|
859
|
+
const newEvent = {
|
|
860
|
+
id: generateEventId(),
|
|
861
|
+
title: 'New Event',
|
|
862
|
+
start: preview.start,
|
|
863
|
+
end: preview.end,
|
|
864
|
+
color: '#3788d8',
|
|
865
|
+
};
|
|
866
|
+
this.stateManager.addEvent(newEvent);
|
|
867
|
+
this.dispatchEvent(new CustomEvent('event-create', {
|
|
868
|
+
detail: { event: newEvent, originalEvent: e },
|
|
869
|
+
bubbles: true,
|
|
870
|
+
}));
|
|
871
|
+
}
|
|
872
|
+
else if (type === 'move' && event) {
|
|
873
|
+
const oldEvent = Object.assign({}, event);
|
|
874
|
+
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
875
|
+
this.stateManager.updateEvent(updatedEvent);
|
|
876
|
+
this.dispatchEvent(new CustomEvent('event-update', {
|
|
877
|
+
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
878
|
+
bubbles: true,
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
881
|
+
else if ((type === 'resize-start' || type === 'resize-end') && event) {
|
|
882
|
+
const oldEvent = Object.assign({}, event);
|
|
883
|
+
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
884
|
+
this.stateManager.updateEvent(updatedEvent);
|
|
885
|
+
this.dispatchEvent(new CustomEvent('event-update', {
|
|
886
|
+
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
887
|
+
bubbles: true,
|
|
888
|
+
}));
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
this.stateManager.endDrag();
|
|
892
|
+
}
|
|
893
|
+
this.exitTouchDragMode();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
handleTouchCancel(_e) {
|
|
897
|
+
this.cancelTouchHold();
|
|
898
|
+
if (this.isTouchDragMode) {
|
|
899
|
+
this.stateManager.endDrag();
|
|
900
|
+
this.exitTouchDragMode();
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
cancelTouchHold() {
|
|
904
|
+
if (this.touchHoldTimer) {
|
|
905
|
+
clearTimeout(this.touchHoldTimer);
|
|
906
|
+
this.touchHoldTimer = null;
|
|
907
|
+
}
|
|
908
|
+
// Remove pending visual feedback
|
|
909
|
+
if (this.touchHoldTarget) {
|
|
910
|
+
this.touchHoldTarget.classList.remove('touch-hold-pending');
|
|
911
|
+
this.touchHoldTarget.classList.remove('touch-hold-active');
|
|
912
|
+
}
|
|
913
|
+
this.touchStartPosition = null;
|
|
914
|
+
}
|
|
915
|
+
exitTouchDragMode() {
|
|
916
|
+
this.isTouchDragMode = false;
|
|
917
|
+
this.touchStartPosition = null;
|
|
918
|
+
this.touchHoldTarget = null;
|
|
919
|
+
// Remove visual feedback
|
|
920
|
+
const container = this.shadow.querySelector('.scheduler-container');
|
|
921
|
+
container === null || container === void 0 ? void 0 : container.classList.remove('touch-drag-mode');
|
|
922
|
+
// Remove active classes from all elements
|
|
923
|
+
this.shadow.querySelectorAll('.touch-hold-active, .touch-hold-pending').forEach((el) => {
|
|
924
|
+
el.classList.remove('touch-hold-active', 'touch-hold-pending');
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
triggerHapticFeedback() {
|
|
928
|
+
// Use Vibration API if available
|
|
929
|
+
if ('vibrate' in navigator) {
|
|
930
|
+
navigator.vibrate(50); // Short vibration (50ms)
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
startDragFromTouch(type, event, clientX, clientY, slotEl) {
|
|
934
|
+
const slot = slotEl
|
|
935
|
+
? this.getSlotFromElement(slotEl)
|
|
936
|
+
: this.getSlotAtPosition(clientX, clientY);
|
|
937
|
+
if (!slot)
|
|
938
|
+
return;
|
|
939
|
+
let preview;
|
|
940
|
+
if (type === 'create') {
|
|
941
|
+
preview = {
|
|
942
|
+
start: slot.start,
|
|
943
|
+
end: slot.end,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
else if (event) {
|
|
947
|
+
preview = {
|
|
948
|
+
start: event.start,
|
|
949
|
+
end: event.end,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
this.stateManager.startDrag({
|
|
956
|
+
type,
|
|
957
|
+
event,
|
|
958
|
+
startSlot: slot,
|
|
959
|
+
currentSlot: slot,
|
|
960
|
+
preview,
|
|
961
|
+
originalEvent: event ? Object.assign({}, event) : undefined,
|
|
962
|
+
meta: type.startsWith('resize-')
|
|
963
|
+
? { resizeHandle: type.replace('resize-', '') }
|
|
964
|
+
: undefined,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
MpScheduler.observedAttributes = [
|
|
969
|
+
'view',
|
|
970
|
+
'date',
|
|
971
|
+
'locale',
|
|
972
|
+
'first-day-of-week',
|
|
973
|
+
'slot-duration',
|
|
974
|
+
'time-format',
|
|
975
|
+
'editable',
|
|
976
|
+
'selectable',
|
|
977
|
+
];
|
|
978
|
+
// Register the custom element
|
|
979
|
+
if (typeof customElements !== 'undefined' && !customElements.get('mp-scheduler')) {
|
|
980
|
+
customElements.define('mp-scheduler', MpScheduler);
|
|
981
|
+
}
|
|
982
|
+
//# sourceMappingURL=mp-scheduler.js.map
|