@mintplayer/scheduler-wc 1.0.1 → 1.1.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 +1 -1
- package/src/components/mp-scheduler.d.ts +16 -41
- package/src/components/mp-scheduler.js +126 -575
- package/src/components/mp-scheduler.js.map +1 -1
- package/src/drag/drag-manager.d.ts +90 -0
- package/src/drag/drag-manager.js +201 -0
- package/src/drag/drag-manager.js.map +1 -0
- package/src/drag/drag-preview.d.ts +42 -0
- package/src/drag/drag-preview.js +87 -0
- package/src/drag/drag-preview.js.map +1 -0
- package/src/drag/drag-state-machine.d.ts +102 -0
- package/src/drag/drag-state-machine.js +320 -0
- package/src/drag/drag-state-machine.js.map +1 -0
- package/src/drag/drag-types.d.ts +104 -0
- package/src/drag/drag-types.js +8 -0
- package/src/drag/drag-types.js.map +1 -0
- package/src/drag/index.d.ts +4 -0
- package/src/drag/index.js +5 -0
- package/src/drag/index.js.map +1 -0
- package/src/events/event-types.d.ts +43 -0
- package/src/events/event-types.js +2 -0
- package/src/events/event-types.js.map +1 -0
- package/src/events/index.d.ts +2 -0
- package/src/events/index.js +3 -0
- package/src/events/index.js.map +1 -0
- package/src/events/scheduler-event-emitter.d.ts +46 -0
- package/src/events/scheduler-event-emitter.js +70 -0
- package/src/events/scheduler-event-emitter.js.map +1 -0
- package/src/input/index.d.ts +2 -0
- package/src/input/index.js +3 -0
- package/src/input/index.js.map +1 -0
- package/src/input/input-handler.d.ts +93 -0
- package/src/input/input-handler.js +340 -0
- package/src/input/input-handler.js.map +1 -0
- package/src/input/pointer-event.d.ts +39 -0
- package/src/input/pointer-event.js +41 -0
- package/src/input/pointer-event.js.map +1 -0
- package/src/styles/scheduler.styles.d.ts +1 -1
- package/src/styles/scheduler.styles.js +14 -0
- package/src/styles/scheduler.styles.js.map +1 -1
|
@@ -6,58 +6,67 @@ import { WeekView } from '../views/week-view';
|
|
|
6
6
|
import { DayView } from '../views/day-view';
|
|
7
7
|
import { TimelineView } from '../views/timeline-view';
|
|
8
8
|
import { schedulerStyles } from '../styles/scheduler.styles';
|
|
9
|
+
import { DragManager } from '../drag';
|
|
10
|
+
import { InputHandler } from '../input';
|
|
11
|
+
import { SchedulerEventEmitter } from '../events';
|
|
9
12
|
/**
|
|
10
13
|
* MpScheduler Web Component
|
|
11
14
|
*
|
|
12
|
-
* A fully-featured scheduler/calendar component
|
|
15
|
+
* A fully-featured scheduler/calendar component.
|
|
16
|
+
* Refactored for clarity with separated concerns:
|
|
17
|
+
* - DragManager: Handles all drag operations
|
|
18
|
+
* - InputHandler: Normalizes mouse/touch input
|
|
19
|
+
* - SchedulerEventEmitter: Dispatches custom events
|
|
13
20
|
*/
|
|
14
21
|
export class MpScheduler extends HTMLElement {
|
|
15
22
|
constructor() {
|
|
16
23
|
super();
|
|
17
24
|
this.currentView = null;
|
|
25
|
+
this.currentViewType = null;
|
|
18
26
|
this.contentContainer = null;
|
|
19
27
|
// Track previous state for change detection
|
|
20
28
|
this.previousView = null;
|
|
21
29
|
this.previousDate = null;
|
|
22
30
|
this.previousSelectedEventId = null;
|
|
23
|
-
//
|
|
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
|
-
// RAF scheduling for drag updates (ensures browser can repaint during drag)
|
|
31
|
+
// RAF scheduling for drag updates
|
|
34
32
|
this.pendingDragUpdate = null;
|
|
35
33
|
this.latestDragState = null;
|
|
36
34
|
this.shadow = this.attachShadow({ mode: 'open' });
|
|
37
35
|
this.stateManager = new SchedulerStateManager();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
this.
|
|
36
|
+
this.eventEmitter = new SchedulerEventEmitter(this);
|
|
37
|
+
// Initialize drag manager
|
|
38
|
+
this.dragManager = new DragManager(this.stateManager);
|
|
39
|
+
this.dragManager.setSlotResolver((x, y) => this.getSlotAtPosition(x, y));
|
|
40
|
+
// Initialize input handler
|
|
41
|
+
this.inputHandler = new InputHandler({
|
|
42
|
+
shadowRoot: this.shadow,
|
|
43
|
+
getEventById: (id) => this.getEventById(id),
|
|
44
|
+
isEditable: () => { var _a; return (_a = this.stateManager.getState().options.editable) !== null && _a !== void 0 ? _a : true; },
|
|
45
|
+
isSelectable: () => { var _a; return (_a = this.stateManager.getState().options.selectable) !== null && _a !== void 0 ? _a : true; },
|
|
46
|
+
}, {
|
|
47
|
+
onPointerDown: (pointer, target, immediate) => this.handlePointerDown(pointer, target, immediate),
|
|
48
|
+
onPointerMove: (pointer) => this.handlePointerMove(pointer),
|
|
49
|
+
onPointerUp: (pointer) => this.handlePointerUp(pointer),
|
|
50
|
+
onClick: (pointer, target) => this.handleClick(pointer, target),
|
|
51
|
+
onDoubleClick: (pointer, target) => this.handleDoubleClick(pointer, target),
|
|
52
|
+
});
|
|
53
|
+
// Bind keyboard handler
|
|
44
54
|
this.boundHandleKeyDown = this.handleKeyDown.bind(this);
|
|
45
|
-
this.boundHandleTouchStart = this.handleTouchStart.bind(this);
|
|
46
|
-
this.boundHandleTouchMove = this.handleTouchMove.bind(this);
|
|
47
|
-
this.boundHandleTouchEnd = this.handleTouchEnd.bind(this);
|
|
48
|
-
this.boundHandleTouchCancel = this.handleTouchCancel.bind(this);
|
|
49
55
|
// Subscribe to state changes
|
|
50
56
|
this.stateManager.subscribe((state) => this.onStateChange(state));
|
|
51
57
|
}
|
|
52
58
|
connectedCallback() {
|
|
53
59
|
this.render();
|
|
54
|
-
this.
|
|
60
|
+
this.inputHandler.attach();
|
|
61
|
+
this.addEventListener('keydown', this.boundHandleKeyDown);
|
|
55
62
|
}
|
|
56
63
|
disconnectedCallback() {
|
|
57
64
|
var _a;
|
|
58
|
-
this.
|
|
65
|
+
this.inputHandler.detach();
|
|
66
|
+
this.removeEventListener('keydown', this.boundHandleKeyDown);
|
|
59
67
|
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
60
|
-
|
|
68
|
+
this.dragManager.destroy();
|
|
69
|
+
// Cancel any pending RAF
|
|
61
70
|
if (this.pendingDragUpdate !== null) {
|
|
62
71
|
cancelAnimationFrame(this.pendingDragUpdate);
|
|
63
72
|
this.pendingDragUpdate = null;
|
|
@@ -87,7 +96,9 @@ export class MpScheduler extends HTMLElement {
|
|
|
87
96
|
if (newValue) {
|
|
88
97
|
const day = parseInt(newValue, 10);
|
|
89
98
|
if (day >= 0 && day <= 6) {
|
|
90
|
-
this.stateManager.setOptions({
|
|
99
|
+
this.stateManager.setOptions({
|
|
100
|
+
firstDayOfWeek: day,
|
|
101
|
+
});
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
break;
|
|
@@ -109,7 +120,9 @@ export class MpScheduler extends HTMLElement {
|
|
|
109
120
|
break;
|
|
110
121
|
}
|
|
111
122
|
}
|
|
123
|
+
// ============================================
|
|
112
124
|
// Public API
|
|
125
|
+
// ============================================
|
|
113
126
|
get view() {
|
|
114
127
|
return this.stateManager.getState().view;
|
|
115
128
|
}
|
|
@@ -183,27 +196,23 @@ export class MpScheduler extends HTMLElement {
|
|
|
183
196
|
}
|
|
184
197
|
refetchEvents() {
|
|
185
198
|
var _a;
|
|
186
|
-
// Trigger re-render
|
|
187
199
|
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.update(this.stateManager.getState());
|
|
188
200
|
}
|
|
189
|
-
//
|
|
201
|
+
// ============================================
|
|
202
|
+
// Rendering
|
|
203
|
+
// ============================================
|
|
190
204
|
render() {
|
|
191
|
-
// Add styles
|
|
192
205
|
const style = document.createElement('style');
|
|
193
206
|
style.textContent = schedulerStyles;
|
|
194
207
|
this.shadow.appendChild(style);
|
|
195
|
-
// Create container
|
|
196
208
|
const container = document.createElement('div');
|
|
197
209
|
container.className = 'scheduler-container';
|
|
198
|
-
// Header
|
|
199
210
|
const header = this.createHeader();
|
|
200
211
|
container.appendChild(header);
|
|
201
|
-
// Content
|
|
202
212
|
this.contentContainer = document.createElement('div');
|
|
203
213
|
this.contentContainer.className = 'scheduler-content';
|
|
204
214
|
container.appendChild(this.contentContainer);
|
|
205
215
|
this.shadow.appendChild(container);
|
|
206
|
-
// Initial view render
|
|
207
216
|
this.renderView();
|
|
208
217
|
}
|
|
209
218
|
createHeader() {
|
|
@@ -276,7 +285,14 @@ export class MpScheduler extends HTMLElement {
|
|
|
276
285
|
case 'timeline': {
|
|
277
286
|
const weekStart = dateService.getWeekStart(date, options.firstDayOfWeek);
|
|
278
287
|
const weekEnd = dateService.addDays(weekStart, 6);
|
|
279
|
-
titleText = `${dateService.formatDate(weekStart, options.locale, {
|
|
288
|
+
titleText = `${dateService.formatDate(weekStart, options.locale, {
|
|
289
|
+
month: 'short',
|
|
290
|
+
day: 'numeric',
|
|
291
|
+
})} - ${dateService.formatDate(weekEnd, options.locale, {
|
|
292
|
+
month: 'short',
|
|
293
|
+
day: 'numeric',
|
|
294
|
+
year: 'numeric',
|
|
295
|
+
})}`;
|
|
280
296
|
break;
|
|
281
297
|
}
|
|
282
298
|
case 'day':
|
|
@@ -294,55 +310,58 @@ export class MpScheduler extends HTMLElement {
|
|
|
294
310
|
var _a, _b;
|
|
295
311
|
if (!this.contentContainer)
|
|
296
312
|
return;
|
|
297
|
-
// Destroy previous view
|
|
298
313
|
(_a = this.currentView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
299
314
|
const state = this.stateManager.getState();
|
|
300
|
-
// Create new view
|
|
301
315
|
switch (state.view) {
|
|
302
316
|
case 'year':
|
|
303
317
|
this.currentView = new YearView(this.contentContainer, state);
|
|
318
|
+
this.currentViewType = 'year';
|
|
304
319
|
break;
|
|
305
320
|
case 'month':
|
|
306
321
|
this.currentView = new MonthView(this.contentContainer, state);
|
|
322
|
+
this.currentViewType = 'month';
|
|
307
323
|
break;
|
|
308
324
|
case 'week':
|
|
309
325
|
this.currentView = new WeekView(this.contentContainer, state);
|
|
326
|
+
this.currentViewType = 'week';
|
|
310
327
|
break;
|
|
311
328
|
case 'day':
|
|
312
329
|
this.currentView = new DayView(this.contentContainer, state);
|
|
330
|
+
this.currentViewType = 'day';
|
|
313
331
|
break;
|
|
314
332
|
case 'timeline':
|
|
315
333
|
this.currentView = new TimelineView(this.contentContainer, state);
|
|
334
|
+
this.currentViewType = 'timeline';
|
|
316
335
|
break;
|
|
317
336
|
}
|
|
318
337
|
(_b = this.currentView) === null || _b === void 0 ? void 0 : _b.render();
|
|
319
338
|
}
|
|
339
|
+
// ============================================
|
|
340
|
+
// State Change Handling
|
|
341
|
+
// ============================================
|
|
320
342
|
onStateChange(state) {
|
|
343
|
+
this.detectAndEmitChanges(state);
|
|
344
|
+
this.updateUI(state);
|
|
345
|
+
}
|
|
346
|
+
detectAndEmitChanges(state) {
|
|
321
347
|
var _a, _b;
|
|
322
|
-
// Detect view/date changes and dispatch events
|
|
323
348
|
const viewChanged = this.previousView !== null && this.previousView !== state.view;
|
|
324
|
-
const dateChanged = this.previousDate !== null &&
|
|
349
|
+
const dateChanged = this.previousDate !== null &&
|
|
350
|
+
this.previousDate.getTime() !== state.date.getTime();
|
|
325
351
|
const selectedEventId = (_b = (_a = state.selectedEvent) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
326
|
-
const selectionChanged = this.previousSelectedEventId !== null &&
|
|
327
|
-
|
|
352
|
+
const selectionChanged = this.previousSelectedEventId !== null &&
|
|
353
|
+
this.previousSelectedEventId !== selectedEventId;
|
|
328
354
|
if (viewChanged || dateChanged) {
|
|
329
|
-
this.
|
|
330
|
-
detail: { view: state.view, date: state.date },
|
|
331
|
-
bubbles: true,
|
|
332
|
-
}));
|
|
355
|
+
this.eventEmitter.emitViewChange(state.view, state.date);
|
|
333
356
|
}
|
|
334
|
-
// Dispatch selection-change event if selected event changed (but not on initial render)
|
|
335
357
|
if (selectionChanged) {
|
|
336
|
-
this.
|
|
337
|
-
detail: { selectedEvent: state.selectedEvent },
|
|
338
|
-
bubbles: true,
|
|
339
|
-
}));
|
|
358
|
+
this.eventEmitter.emitSelectionChange(state.selectedEvent);
|
|
340
359
|
}
|
|
341
|
-
// Update previous state tracking
|
|
342
360
|
this.previousView = state.view;
|
|
343
361
|
this.previousDate = new Date(state.date);
|
|
344
362
|
this.previousSelectedEventId = selectedEventId;
|
|
345
|
-
|
|
363
|
+
}
|
|
364
|
+
updateUI(state) {
|
|
346
365
|
this.updateTitle();
|
|
347
366
|
// Update view switcher active state
|
|
348
367
|
const buttons = this.shadow.querySelectorAll('.scheduler-view-switcher button');
|
|
@@ -352,42 +371,24 @@ export class MpScheduler extends HTMLElement {
|
|
|
352
371
|
});
|
|
353
372
|
// Update or re-render view
|
|
354
373
|
if (this.currentView) {
|
|
355
|
-
|
|
356
|
-
const viewTypeChanged = this.viewTypeChanged(state.view);
|
|
357
|
-
if (viewTypeChanged) {
|
|
374
|
+
if (this.currentViewType !== state.view) {
|
|
358
375
|
this.renderView();
|
|
359
376
|
}
|
|
377
|
+
else if (state.dragState || state.previewEvent) {
|
|
378
|
+
this.scheduleDragUpdate(state);
|
|
379
|
+
}
|
|
360
380
|
else {
|
|
361
|
-
|
|
362
|
-
// can repaint between updates. This fixes the issue where drag previews
|
|
363
|
-
// don't appear in production builds with zoneless Angular.
|
|
364
|
-
if (state.dragState || state.previewEvent) {
|
|
365
|
-
this.scheduleDragUpdate(state);
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
// For non-drag updates, process synchronously for responsiveness
|
|
369
|
-
this.currentView.update(state);
|
|
370
|
-
}
|
|
381
|
+
this.currentView.update(state);
|
|
371
382
|
}
|
|
372
383
|
}
|
|
373
384
|
}
|
|
374
|
-
/**
|
|
375
|
-
* Schedule a drag-related view update using requestAnimationFrame.
|
|
376
|
-
* This ensures the browser has time to repaint between drag updates,
|
|
377
|
-
* which is necessary for drag previews to appear in production builds.
|
|
378
|
-
*/
|
|
379
385
|
scheduleDragUpdate(state) {
|
|
380
|
-
// Store the latest state
|
|
381
386
|
this.latestDragState = state;
|
|
382
|
-
// If there's already a pending update, don't schedule another one
|
|
383
|
-
// The pending update will use the latest state when it executes
|
|
384
387
|
if (this.pendingDragUpdate !== null) {
|
|
385
388
|
return;
|
|
386
389
|
}
|
|
387
|
-
// Schedule the update for the next animation frame
|
|
388
390
|
this.pendingDragUpdate = requestAnimationFrame(() => {
|
|
389
391
|
this.pendingDragUpdate = null;
|
|
390
|
-
// Use the latest state (in case multiple updates were batched)
|
|
391
392
|
const stateToApply = this.latestDragState;
|
|
392
393
|
this.latestDragState = null;
|
|
393
394
|
if (stateToApply && this.currentView) {
|
|
@@ -395,180 +396,60 @@ export class MpScheduler extends HTMLElement {
|
|
|
395
396
|
}
|
|
396
397
|
});
|
|
397
398
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
// ============================================
|
|
400
|
+
// Input Handling (Callbacks from InputHandler)
|
|
401
|
+
// ============================================
|
|
402
|
+
handlePointerDown(pointer, target, immediate) {
|
|
403
|
+
this.dragManager.handlePointerDown(pointer, target, immediate);
|
|
403
404
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
root.addEventListener('mousedown', this.boundHandleMouseDown);
|
|
407
|
-
document.addEventListener('mousemove', this.boundHandleMouseMove);
|
|
408
|
-
document.addEventListener('mouseup', this.boundHandleMouseUp);
|
|
409
|
-
root.addEventListener('click', this.boundHandleClick);
|
|
410
|
-
root.addEventListener('dblclick', this.boundHandleDblClick);
|
|
411
|
-
this.addEventListener('keydown', this.boundHandleKeyDown);
|
|
412
|
-
// Touch events
|
|
413
|
-
root.addEventListener('touchstart', this.boundHandleTouchStart, { passive: false });
|
|
414
|
-
root.addEventListener('touchmove', this.boundHandleTouchMove, { passive: false });
|
|
415
|
-
root.addEventListener('touchend', this.boundHandleTouchEnd);
|
|
416
|
-
root.addEventListener('touchcancel', this.boundHandleTouchCancel);
|
|
405
|
+
handlePointerMove(pointer) {
|
|
406
|
+
this.dragManager.handlePointerMove(pointer);
|
|
417
407
|
}
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
document.removeEventListener('mouseup', this.boundHandleMouseUp);
|
|
423
|
-
root.removeEventListener('click', this.boundHandleClick);
|
|
424
|
-
root.removeEventListener('dblclick', this.boundHandleDblClick);
|
|
425
|
-
this.removeEventListener('keydown', this.boundHandleKeyDown);
|
|
426
|
-
// Touch events
|
|
427
|
-
root.removeEventListener('touchstart', this.boundHandleTouchStart);
|
|
428
|
-
root.removeEventListener('touchmove', this.boundHandleTouchMove);
|
|
429
|
-
root.removeEventListener('touchend', this.boundHandleTouchEnd);
|
|
430
|
-
root.removeEventListener('touchcancel', this.boundHandleTouchCancel);
|
|
431
|
-
this.cancelTouchHold();
|
|
432
|
-
}
|
|
433
|
-
handleMouseDown(e) {
|
|
434
|
-
const state = this.stateManager.getState();
|
|
435
|
-
if (!state.options.editable)
|
|
436
|
-
return;
|
|
437
|
-
const target = e.target;
|
|
438
|
-
// Check for resize handle - start drag immediately (no click behavior)
|
|
439
|
-
const resizeHandle = target.closest('.resize-handle');
|
|
440
|
-
if (resizeHandle) {
|
|
441
|
-
const eventEl = resizeHandle.closest('.scheduler-event');
|
|
442
|
-
const eventId = eventEl === null || eventEl === void 0 ? void 0 : eventEl.dataset['eventId'];
|
|
443
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
444
|
-
if (event) {
|
|
445
|
-
const handleType = resizeHandle.dataset['handle'];
|
|
446
|
-
this.pendingDrag = {
|
|
447
|
-
type: ('resize-' + handleType),
|
|
448
|
-
event,
|
|
449
|
-
startX: e.clientX,
|
|
450
|
-
startY: e.clientY,
|
|
451
|
-
};
|
|
452
|
-
e.preventDefault();
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
// Check for event - set up pending drag (actual drag starts on mouse move)
|
|
457
|
-
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
458
|
-
if (eventEl && !eventEl.classList.contains('preview')) {
|
|
459
|
-
const eventId = eventEl.dataset['eventId'];
|
|
460
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
461
|
-
if (event && event.draggable !== false) {
|
|
462
|
-
this.pendingDrag = {
|
|
463
|
-
type: 'move',
|
|
464
|
-
event,
|
|
465
|
-
startX: e.clientX,
|
|
466
|
-
startY: e.clientY,
|
|
467
|
-
};
|
|
468
|
-
e.preventDefault();
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
// Check for slot - set up pending drag for create
|
|
473
|
-
const slotEl = target.closest('.scheduler-time-slot, .scheduler-timeline-slot');
|
|
474
|
-
if (slotEl && state.options.selectable) {
|
|
475
|
-
this.pendingDrag = {
|
|
476
|
-
type: 'create',
|
|
477
|
-
event: null,
|
|
478
|
-
startX: e.clientX,
|
|
479
|
-
startY: e.clientY,
|
|
480
|
-
slotEl,
|
|
481
|
-
};
|
|
482
|
-
e.preventDefault();
|
|
408
|
+
handlePointerUp(pointer) {
|
|
409
|
+
const result = this.dragManager.handlePointerUp(pointer);
|
|
410
|
+
if (result) {
|
|
411
|
+
this.handleDragComplete(result, pointer.originalEvent);
|
|
483
412
|
}
|
|
484
413
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (distance >= this.DRAG_THRESHOLD) {
|
|
492
|
-
// Start the actual drag
|
|
493
|
-
this.startDrag(this.pendingDrag.type, this.pendingDrag.event, e, this.pendingDrag.slotEl);
|
|
494
|
-
this.pendingDrag = null;
|
|
414
|
+
handleDragComplete(result, originalEvent) {
|
|
415
|
+
if (result.wasClick) {
|
|
416
|
+
// It was a click, not a drag
|
|
417
|
+
if (result.event) {
|
|
418
|
+
this.stateManager.setSelectedEvent(result.event);
|
|
419
|
+
this.eventEmitter.emitEventClick(result.event, originalEvent);
|
|
495
420
|
}
|
|
496
421
|
return;
|
|
497
422
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const slot = this.getSlotAtPosition(e.clientX, e.clientY);
|
|
502
|
-
if (!slot)
|
|
503
|
-
return;
|
|
504
|
-
const preview = this.calculatePreview(state.dragState.type, state.dragState, slot);
|
|
505
|
-
if (preview) {
|
|
506
|
-
this.stateManager.updateDrag(slot, preview);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
handleMouseUp(e) {
|
|
510
|
-
// If we had a pending drag that never started, it's a click
|
|
511
|
-
if (this.pendingDrag) {
|
|
512
|
-
const { type, event } = this.pendingDrag;
|
|
513
|
-
this.pendingDrag = null;
|
|
514
|
-
// If it was on an event, select it
|
|
515
|
-
if (type === 'move' && event) {
|
|
516
|
-
this.stateManager.setSelectedEvent(event);
|
|
517
|
-
this.dispatchEvent(new CustomEvent('event-click', {
|
|
518
|
-
detail: { event, originalEvent: e },
|
|
519
|
-
bubbles: true,
|
|
520
|
-
}));
|
|
521
|
-
}
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
const state = this.stateManager.getState();
|
|
525
|
-
if (!state.dragState)
|
|
526
|
-
return;
|
|
527
|
-
const { type, event, preview } = state.dragState;
|
|
528
|
-
// Finalize the drag operation
|
|
529
|
-
if (preview) {
|
|
530
|
-
if (type === 'create') {
|
|
531
|
-
// Create new event
|
|
423
|
+
// Handle actual drag completion
|
|
424
|
+
switch (result.type) {
|
|
425
|
+
case 'create': {
|
|
532
426
|
const newEvent = {
|
|
533
427
|
id: generateEventId(),
|
|
534
428
|
title: 'New Event',
|
|
535
|
-
start: preview.start,
|
|
536
|
-
end: preview.end,
|
|
429
|
+
start: result.preview.start,
|
|
430
|
+
end: result.preview.end,
|
|
537
431
|
color: '#3788d8',
|
|
538
432
|
};
|
|
539
433
|
this.stateManager.addEvent(newEvent);
|
|
540
|
-
this.
|
|
541
|
-
|
|
542
|
-
bubbles: true,
|
|
543
|
-
}));
|
|
544
|
-
}
|
|
545
|
-
else if (type === 'move' && event) {
|
|
546
|
-
// Move event
|
|
547
|
-
const oldEvent = Object.assign({}, event);
|
|
548
|
-
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
549
|
-
this.stateManager.updateEvent(updatedEvent);
|
|
550
|
-
this.dispatchEvent(new CustomEvent('event-update', {
|
|
551
|
-
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
552
|
-
bubbles: true,
|
|
553
|
-
}));
|
|
434
|
+
this.eventEmitter.emitEventCreate(newEvent, originalEvent);
|
|
435
|
+
break;
|
|
554
436
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
437
|
+
case 'move':
|
|
438
|
+
case 'resize-start':
|
|
439
|
+
case 'resize-end': {
|
|
440
|
+
if (result.event && result.originalEvent) {
|
|
441
|
+
const updatedEvent = Object.assign(Object.assign({}, result.event), { start: result.preview.start, end: result.preview.end });
|
|
442
|
+
this.stateManager.updateEvent(updatedEvent);
|
|
443
|
+
this.eventEmitter.emitEventUpdate(updatedEvent, result.originalEvent, originalEvent);
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
564
446
|
}
|
|
565
447
|
}
|
|
566
|
-
this.stateManager.endDrag();
|
|
567
448
|
}
|
|
568
|
-
handleClick(
|
|
569
|
-
const
|
|
449
|
+
handleClick(pointer, target) {
|
|
450
|
+
const targetEl = pointer.target;
|
|
570
451
|
// Group toggle
|
|
571
|
-
const toggle =
|
|
452
|
+
const toggle = targetEl.closest('.expand-toggle');
|
|
572
453
|
if (toggle) {
|
|
573
454
|
const groupId = toggle.dataset['groupId'];
|
|
574
455
|
if (groupId) {
|
|
@@ -577,25 +458,16 @@ export class MpScheduler extends HTMLElement {
|
|
|
577
458
|
return;
|
|
578
459
|
}
|
|
579
460
|
}
|
|
580
|
-
// Event clicks are handled in handleMouseUp (via pendingDrag)
|
|
581
|
-
// Skip event click handling here to avoid duplicates
|
|
582
|
-
const eventEl = target.closest('.scheduler-event');
|
|
583
|
-
if (eventEl) {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
461
|
// Date click
|
|
587
|
-
const dayEl =
|
|
462
|
+
const dayEl = targetEl.closest('[data-date]');
|
|
588
463
|
if (dayEl) {
|
|
589
464
|
const dateStr = dayEl.dataset['date'];
|
|
590
465
|
if (dateStr) {
|
|
591
|
-
this.
|
|
592
|
-
detail: { date: new Date(dateStr), originalEvent: e },
|
|
593
|
-
bubbles: true,
|
|
594
|
-
}));
|
|
466
|
+
this.eventEmitter.emitDateClick(new Date(dateStr), pointer.originalEvent);
|
|
595
467
|
}
|
|
596
468
|
}
|
|
597
469
|
// Month click in year view
|
|
598
|
-
const monthHeader =
|
|
470
|
+
const monthHeader = targetEl.closest('.scheduler-year-month-header');
|
|
599
471
|
if (monthHeader) {
|
|
600
472
|
const monthStr = monthHeader.dataset['month'];
|
|
601
473
|
if (monthStr) {
|
|
@@ -604,7 +476,7 @@ export class MpScheduler extends HTMLElement {
|
|
|
604
476
|
}
|
|
605
477
|
}
|
|
606
478
|
// More link click
|
|
607
|
-
const moreLink =
|
|
479
|
+
const moreLink = targetEl.closest('.scheduler-more-link');
|
|
608
480
|
if (moreLink) {
|
|
609
481
|
const dateStr = moreLink.dataset['date'];
|
|
610
482
|
if (dateStr) {
|
|
@@ -613,18 +485,9 @@ export class MpScheduler extends HTMLElement {
|
|
|
613
485
|
}
|
|
614
486
|
}
|
|
615
487
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (eventEl) {
|
|
620
|
-
const eventId = eventEl.dataset['eventId'];
|
|
621
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
622
|
-
if (event) {
|
|
623
|
-
this.dispatchEvent(new CustomEvent('event-dblclick', {
|
|
624
|
-
detail: { event, originalEvent: e },
|
|
625
|
-
bubbles: true,
|
|
626
|
-
}));
|
|
627
|
-
}
|
|
488
|
+
handleDoubleClick(pointer, target) {
|
|
489
|
+
if (target.type === 'event' && target.event) {
|
|
490
|
+
this.eventEmitter.emitEventDblClick(target.event, pointer.originalEvent);
|
|
628
491
|
}
|
|
629
492
|
}
|
|
630
493
|
handleKeyDown(e) {
|
|
@@ -666,84 +529,22 @@ export class MpScheduler extends HTMLElement {
|
|
|
666
529
|
case 'Delete':
|
|
667
530
|
case 'Backspace':
|
|
668
531
|
if (state.selectedEvent) {
|
|
669
|
-
this.
|
|
670
|
-
detail: { event: state.selectedEvent },
|
|
671
|
-
bubbles: true,
|
|
672
|
-
}));
|
|
532
|
+
this.eventEmitter.emitEventDelete(state.selectedEvent);
|
|
673
533
|
}
|
|
674
534
|
break;
|
|
675
535
|
case 'Escape':
|
|
676
|
-
if (
|
|
677
|
-
this.
|
|
536
|
+
if (this.dragManager.isDragging()) {
|
|
537
|
+
this.dragManager.cancel();
|
|
678
538
|
}
|
|
679
539
|
break;
|
|
680
540
|
}
|
|
681
541
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
: this.getSlotAtPosition(mouseEvent.clientX, mouseEvent.clientY);
|
|
686
|
-
if (!slot)
|
|
687
|
-
return;
|
|
688
|
-
let preview;
|
|
689
|
-
if (type === 'create') {
|
|
690
|
-
preview = {
|
|
691
|
-
start: slot.start,
|
|
692
|
-
end: slot.end,
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
else if (event) {
|
|
696
|
-
preview = {
|
|
697
|
-
start: event.start,
|
|
698
|
-
end: event.end,
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
this.stateManager.startDrag({
|
|
705
|
-
type,
|
|
706
|
-
event,
|
|
707
|
-
startSlot: slot,
|
|
708
|
-
currentSlot: slot,
|
|
709
|
-
preview,
|
|
710
|
-
originalEvent: event ? Object.assign({}, event) : undefined,
|
|
711
|
-
meta: type.startsWith('resize-')
|
|
712
|
-
? { resizeHandle: type.replace('resize-', '') }
|
|
713
|
-
: undefined,
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
calculatePreview(type, dragState, currentSlot) {
|
|
717
|
-
const { startSlot, event, originalEvent } = dragState;
|
|
718
|
-
if (type === 'create') {
|
|
719
|
-
// Extend selection from start slot to current slot
|
|
720
|
-
const start = new Date(Math.min(startSlot.start.getTime(), currentSlot.start.getTime()));
|
|
721
|
-
const end = new Date(Math.max(startSlot.end.getTime(), currentSlot.end.getTime()));
|
|
722
|
-
return { start, end };
|
|
723
|
-
}
|
|
724
|
-
if (type === 'move' && originalEvent) {
|
|
725
|
-
// Calculate offset and apply to event
|
|
726
|
-
const offsetMs = currentSlot.start.getTime() - startSlot.start.getTime();
|
|
727
|
-
const duration = originalEvent.end.getTime() - originalEvent.start.getTime();
|
|
728
|
-
const newStart = new Date(originalEvent.start.getTime() + offsetMs);
|
|
729
|
-
const newEnd = new Date(newStart.getTime() + duration);
|
|
730
|
-
return { start: newStart, end: newEnd };
|
|
731
|
-
}
|
|
732
|
-
if (type === 'resize-start' && originalEvent) {
|
|
733
|
-
// Move start, keep end fixed
|
|
734
|
-
const newStart = new Date(Math.min(currentSlot.start.getTime(), originalEvent.end.getTime() - 1800000));
|
|
735
|
-
return { start: newStart, end: originalEvent.end };
|
|
736
|
-
}
|
|
737
|
-
if (type === 'resize-end' && originalEvent) {
|
|
738
|
-
// Move end, keep start fixed
|
|
739
|
-
const newEnd = new Date(Math.max(currentSlot.end.getTime(), originalEvent.start.getTime() + 1800000));
|
|
740
|
-
return { start: originalEvent.start, end: newEnd };
|
|
741
|
-
}
|
|
742
|
-
return null;
|
|
743
|
-
}
|
|
542
|
+
// ============================================
|
|
543
|
+
// Slot Resolution
|
|
544
|
+
// ============================================
|
|
744
545
|
getSlotAtPosition(clientX, clientY) {
|
|
745
|
-
const
|
|
746
|
-
|
|
546
|
+
const elements = this.shadow.elementsFromPoint(clientX, clientY);
|
|
547
|
+
const slotEl = elements.find((el) => el.matches('.scheduler-time-slot, .scheduler-timeline-slot'));
|
|
747
548
|
return slotEl ? this.getSlotFromElement(slotEl) : null;
|
|
748
549
|
}
|
|
749
550
|
getSlotFromElement(el) {
|
|
@@ -756,256 +557,6 @@ export class MpScheduler extends HTMLElement {
|
|
|
756
557
|
end: new Date(endStr),
|
|
757
558
|
};
|
|
758
559
|
}
|
|
759
|
-
// Touch event handlers
|
|
760
|
-
handleTouchStart(e) {
|
|
761
|
-
const state = this.stateManager.getState();
|
|
762
|
-
if (!state.options.editable)
|
|
763
|
-
return;
|
|
764
|
-
// Only handle single touch
|
|
765
|
-
if (e.touches.length !== 1) {
|
|
766
|
-
this.cancelTouchHold();
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const touch = e.touches[0];
|
|
770
|
-
const target = touch.target;
|
|
771
|
-
this.touchStartPosition = { x: touch.clientX, y: touch.clientY };
|
|
772
|
-
// Check if touching an event or slot that could be dragged
|
|
773
|
-
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
774
|
-
const resizeHandle = target.closest('.resize-handle');
|
|
775
|
-
const slotEl = target.closest('.scheduler-time-slot, .scheduler-timeline-slot');
|
|
776
|
-
if (!eventEl && !slotEl) {
|
|
777
|
-
// Not touching a draggable element
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
// Store the target for the hold callback
|
|
781
|
-
this.touchHoldTarget = eventEl || slotEl;
|
|
782
|
-
// Add visual feedback class
|
|
783
|
-
if (eventEl) {
|
|
784
|
-
eventEl.classList.add('touch-hold-pending');
|
|
785
|
-
}
|
|
786
|
-
else if (slotEl) {
|
|
787
|
-
slotEl.classList.add('touch-hold-pending');
|
|
788
|
-
}
|
|
789
|
-
// Start the hold timer
|
|
790
|
-
this.touchHoldTimer = setTimeout(() => {
|
|
791
|
-
this.activateTouchDragMode(touch, resizeHandle);
|
|
792
|
-
}, this.TOUCH_HOLD_DURATION);
|
|
793
|
-
}
|
|
794
|
-
activateTouchDragMode(touch, resizeHandle) {
|
|
795
|
-
const state = this.stateManager.getState();
|
|
796
|
-
const target = this.touchHoldTarget;
|
|
797
|
-
if (!target)
|
|
798
|
-
return;
|
|
799
|
-
// Trigger haptic feedback if available
|
|
800
|
-
this.triggerHapticFeedback();
|
|
801
|
-
// Enter touch drag mode
|
|
802
|
-
this.isTouchDragMode = true;
|
|
803
|
-
// Add visual feedback
|
|
804
|
-
const container = this.shadow.querySelector('.scheduler-container');
|
|
805
|
-
container === null || container === void 0 ? void 0 : container.classList.add('touch-drag-mode');
|
|
806
|
-
// Remove pending class, add active class
|
|
807
|
-
target.classList.remove('touch-hold-pending');
|
|
808
|
-
target.classList.add('touch-hold-active');
|
|
809
|
-
// Determine the drag type and start the drag
|
|
810
|
-
const eventEl = target.closest('.scheduler-event:not(.preview)');
|
|
811
|
-
if (resizeHandle) {
|
|
812
|
-
// Resize operation
|
|
813
|
-
const parentEventEl = resizeHandle.closest('.scheduler-event');
|
|
814
|
-
const eventId = parentEventEl === null || parentEventEl === void 0 ? void 0 : parentEventEl.dataset['eventId'];
|
|
815
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
816
|
-
if (event) {
|
|
817
|
-
const handleType = resizeHandle.dataset['handle'];
|
|
818
|
-
this.startDragFromTouch(('resize-' + handleType), event, touch.clientX, touch.clientY);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
else if (eventEl) {
|
|
822
|
-
// Move operation
|
|
823
|
-
const eventId = eventEl.dataset['eventId'];
|
|
824
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
825
|
-
if (event && event.draggable !== false) {
|
|
826
|
-
this.startDragFromTouch('move', event, touch.clientX, touch.clientY);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
else if (target.matches('.scheduler-time-slot, .scheduler-timeline-slot') && state.options.selectable) {
|
|
830
|
-
// Create operation
|
|
831
|
-
this.startDragFromTouch('create', null, touch.clientX, touch.clientY, target);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
handleTouchMove(e) {
|
|
835
|
-
if (e.touches.length !== 1) {
|
|
836
|
-
this.cancelTouchHold();
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
const touch = e.touches[0];
|
|
840
|
-
// If we have a pending touch hold, check if user moved too much
|
|
841
|
-
if (this.touchHoldTimer && this.touchStartPosition) {
|
|
842
|
-
const dx = touch.clientX - this.touchStartPosition.x;
|
|
843
|
-
const dy = touch.clientY - this.touchStartPosition.y;
|
|
844
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
845
|
-
if (distance > this.TOUCH_MOVE_THRESHOLD) {
|
|
846
|
-
// User moved too much, cancel the hold and allow normal scrolling
|
|
847
|
-
this.cancelTouchHold();
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
// Still waiting for hold, prevent default to avoid scroll during hold detection
|
|
851
|
-
// But only if we're close to the threshold
|
|
852
|
-
if (distance < this.TOUCH_MOVE_THRESHOLD / 2) {
|
|
853
|
-
// Don't prevent default yet to allow small movements
|
|
854
|
-
}
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
// If in touch drag mode, handle the drag
|
|
858
|
-
if (this.isTouchDragMode) {
|
|
859
|
-
e.preventDefault(); // Prevent scrolling while dragging
|
|
860
|
-
const state = this.stateManager.getState();
|
|
861
|
-
if (!state.dragState)
|
|
862
|
-
return;
|
|
863
|
-
const slot = this.getSlotAtPosition(touch.clientX, touch.clientY);
|
|
864
|
-
if (!slot)
|
|
865
|
-
return;
|
|
866
|
-
const preview = this.calculatePreview(state.dragState.type, state.dragState, slot);
|
|
867
|
-
if (preview) {
|
|
868
|
-
this.stateManager.updateDrag(slot, preview);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
handleTouchEnd(e) {
|
|
873
|
-
// If we had a pending hold that never activated, treat as a tap
|
|
874
|
-
if (this.touchHoldTimer) {
|
|
875
|
-
this.cancelTouchHold();
|
|
876
|
-
// Handle as a tap/click on the target
|
|
877
|
-
if (this.touchHoldTarget) {
|
|
878
|
-
const eventEl = this.touchHoldTarget.closest('.scheduler-event:not(.preview)');
|
|
879
|
-
if (eventEl) {
|
|
880
|
-
const eventId = eventEl.dataset['eventId'];
|
|
881
|
-
const event = eventId ? this.getEventById(eventId) : null;
|
|
882
|
-
if (event) {
|
|
883
|
-
this.stateManager.setSelectedEvent(event);
|
|
884
|
-
this.dispatchEvent(new CustomEvent('event-click', {
|
|
885
|
-
detail: { event, originalEvent: e },
|
|
886
|
-
bubbles: true,
|
|
887
|
-
}));
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
this.touchHoldTarget = null;
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
// If in touch drag mode, finalize the drag
|
|
895
|
-
if (this.isTouchDragMode) {
|
|
896
|
-
const state = this.stateManager.getState();
|
|
897
|
-
if (state.dragState) {
|
|
898
|
-
const { type, event, preview } = state.dragState;
|
|
899
|
-
if (preview) {
|
|
900
|
-
if (type === 'create') {
|
|
901
|
-
const newEvent = {
|
|
902
|
-
id: generateEventId(),
|
|
903
|
-
title: 'New Event',
|
|
904
|
-
start: preview.start,
|
|
905
|
-
end: preview.end,
|
|
906
|
-
color: '#3788d8',
|
|
907
|
-
};
|
|
908
|
-
this.stateManager.addEvent(newEvent);
|
|
909
|
-
this.dispatchEvent(new CustomEvent('event-create', {
|
|
910
|
-
detail: { event: newEvent, originalEvent: e },
|
|
911
|
-
bubbles: true,
|
|
912
|
-
}));
|
|
913
|
-
}
|
|
914
|
-
else if (type === 'move' && event) {
|
|
915
|
-
const oldEvent = Object.assign({}, event);
|
|
916
|
-
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
917
|
-
this.stateManager.updateEvent(updatedEvent);
|
|
918
|
-
this.dispatchEvent(new CustomEvent('event-update', {
|
|
919
|
-
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
920
|
-
bubbles: true,
|
|
921
|
-
}));
|
|
922
|
-
}
|
|
923
|
-
else if ((type === 'resize-start' || type === 'resize-end') && event) {
|
|
924
|
-
const oldEvent = Object.assign({}, event);
|
|
925
|
-
const updatedEvent = Object.assign(Object.assign({}, event), { start: preview.start, end: preview.end });
|
|
926
|
-
this.stateManager.updateEvent(updatedEvent);
|
|
927
|
-
this.dispatchEvent(new CustomEvent('event-update', {
|
|
928
|
-
detail: { event: updatedEvent, oldEvent, originalEvent: e },
|
|
929
|
-
bubbles: true,
|
|
930
|
-
}));
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
this.stateManager.endDrag();
|
|
934
|
-
}
|
|
935
|
-
this.exitTouchDragMode();
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
handleTouchCancel(_e) {
|
|
939
|
-
this.cancelTouchHold();
|
|
940
|
-
if (this.isTouchDragMode) {
|
|
941
|
-
this.stateManager.endDrag();
|
|
942
|
-
this.exitTouchDragMode();
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
cancelTouchHold() {
|
|
946
|
-
if (this.touchHoldTimer) {
|
|
947
|
-
clearTimeout(this.touchHoldTimer);
|
|
948
|
-
this.touchHoldTimer = null;
|
|
949
|
-
}
|
|
950
|
-
// Remove pending visual feedback
|
|
951
|
-
if (this.touchHoldTarget) {
|
|
952
|
-
this.touchHoldTarget.classList.remove('touch-hold-pending');
|
|
953
|
-
this.touchHoldTarget.classList.remove('touch-hold-active');
|
|
954
|
-
}
|
|
955
|
-
this.touchStartPosition = null;
|
|
956
|
-
}
|
|
957
|
-
exitTouchDragMode() {
|
|
958
|
-
this.isTouchDragMode = false;
|
|
959
|
-
this.touchStartPosition = null;
|
|
960
|
-
this.touchHoldTarget = null;
|
|
961
|
-
// Remove visual feedback
|
|
962
|
-
const container = this.shadow.querySelector('.scheduler-container');
|
|
963
|
-
container === null || container === void 0 ? void 0 : container.classList.remove('touch-drag-mode');
|
|
964
|
-
// Remove active classes from all elements
|
|
965
|
-
this.shadow.querySelectorAll('.touch-hold-active, .touch-hold-pending').forEach((el) => {
|
|
966
|
-
el.classList.remove('touch-hold-active', 'touch-hold-pending');
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
triggerHapticFeedback() {
|
|
970
|
-
// Use Vibration API if available
|
|
971
|
-
if ('vibrate' in navigator) {
|
|
972
|
-
navigator.vibrate(50); // Short vibration (50ms)
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
startDragFromTouch(type, event, clientX, clientY, slotEl) {
|
|
976
|
-
const slot = slotEl
|
|
977
|
-
? this.getSlotFromElement(slotEl)
|
|
978
|
-
: this.getSlotAtPosition(clientX, clientY);
|
|
979
|
-
if (!slot)
|
|
980
|
-
return;
|
|
981
|
-
let preview;
|
|
982
|
-
if (type === 'create') {
|
|
983
|
-
preview = {
|
|
984
|
-
start: slot.start,
|
|
985
|
-
end: slot.end,
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
else if (event) {
|
|
989
|
-
preview = {
|
|
990
|
-
start: event.start,
|
|
991
|
-
end: event.end,
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
else {
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
this.stateManager.startDrag({
|
|
998
|
-
type,
|
|
999
|
-
event,
|
|
1000
|
-
startSlot: slot,
|
|
1001
|
-
currentSlot: slot,
|
|
1002
|
-
preview,
|
|
1003
|
-
originalEvent: event ? Object.assign({}, event) : undefined,
|
|
1004
|
-
meta: type.startsWith('resize-')
|
|
1005
|
-
? { resizeHandle: type.replace('resize-', '') }
|
|
1006
|
-
: undefined,
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
560
|
}
|
|
1010
561
|
MpScheduler.observedAttributes = [
|
|
1011
562
|
'view',
|