@forcecalendar/interface 1.3.0 → 1.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/interface",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "Official interface layer for forceCalendar Core - Enterprise calendar components",
6
6
  "main": "dist/force-calendar-interface.umd.js",
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { BaseComponent } from '../core/BaseComponent.js';
8
8
  import StateManager from '../core/StateManager.js';
9
- import { StyleUtils } from '../utils/StyleUtils.js';
9
+ import { StyleUtils, THEME_PRESETS } from '../utils/StyleUtils.js';
10
10
  import { DateUtils } from '../utils/DateUtils.js';
11
11
  import { DOMUtils } from '../utils/DOMUtils.js';
12
12
 
@@ -26,7 +26,7 @@ export class ForceCalendar extends BaseComponent {
26
26
  };
27
27
 
28
28
  static get observedAttributes() {
29
- return ['view', 'date', 'locale', 'timezone', 'week-starts-on', 'height'];
29
+ return ['view', 'date', 'locale', 'timezone', 'week-starts-on', 'height', 'theme'];
30
30
  }
31
31
 
32
32
  constructor() {
@@ -403,6 +403,39 @@ export class ForceCalendar extends BaseComponent {
403
403
  outline-offset: -2px;
404
404
  }
405
405
 
406
+ .fc-event { touch-action: none; }
407
+
408
+ .fc-dragging {
409
+ opacity: 0.6;
410
+ z-index: 30;
411
+ pointer-events: none;
412
+ }
413
+
414
+ .fc-drop-target {
415
+ outline: 2px dashed var(--fc-primary-color);
416
+ outline-offset: -2px;
417
+ }
418
+
419
+ .fc-resize-handle {
420
+ position: absolute;
421
+ left: 0;
422
+ right: 0;
423
+ bottom: 0;
424
+ height: 6px;
425
+ cursor: ns-resize;
426
+ }
427
+
428
+ .fc-drag-selection {
429
+ position: absolute;
430
+ left: 2px;
431
+ right: 2px;
432
+ background: color-mix(in srgb, var(--fc-primary-color) 18%, transparent);
433
+ border: 1px solid var(--fc-primary-color);
434
+ border-radius: 4px;
435
+ pointer-events: none;
436
+ z-index: 20;
437
+ }
438
+
406
439
  .fc-nav-arrow:focus-visible,
407
440
  .fc-btn:focus-visible,
408
441
  .fc-btn-today:focus-visible,
@@ -768,7 +801,25 @@ export class ForceCalendar extends BaseComponent {
768
801
  return '<div id="calendar-view-container"></div>';
769
802
  }
770
803
 
804
+ /**
805
+ * Apply a named theme preset (e.g. theme="slds") as host-level custom
806
+ * properties so it cascades into the shadow DOM and stays overridable
807
+ * by page-level --fc-* variables.
808
+ */
809
+ _applyTheme(name) {
810
+ const preset = THEME_PRESETS[name];
811
+ for (const token of Object.keys(THEME_PRESETS.slds)) {
812
+ if (preset && preset[token]) {
813
+ this.style.setProperty(token, preset[token]);
814
+ } else {
815
+ this.style.removeProperty(token);
816
+ }
817
+ }
818
+ }
819
+
771
820
  afterRender() {
821
+ this._applyTheme(this.getAttribute('theme'));
822
+
772
823
  // Manually instantiate and mount view renderer (bypasses Locker Service)
773
824
  const container = this.$('#calendar-view-container');
774
825
 
@@ -834,6 +885,14 @@ export class ForceCalendar extends BaseComponent {
834
885
  }
835
886
  });
836
887
 
888
+ // Drag-to-create emits a range: surface it and open the form prefilled
889
+ this.addListener(this.shadowRoot, 'range-select', e => {
890
+ this.emit('calendar-range-select', e.detail);
891
+ if (modal) {
892
+ modal.open(e.detail.start);
893
+ }
894
+ });
895
+
837
896
  // Handle event saving
838
897
  if (modal) {
839
898
  this.addListener(modal, 'save', e => {
@@ -0,0 +1,362 @@
1
+ /**
2
+ * DragController - pointer-driven interactions for calendar views.
3
+ *
4
+ * Implements drag-to-move (month and time grids), drag-to-resize, and
5
+ * drag-to-create using Pointer Events only: no HTML5 drag-and-drop (which
6
+ * misbehaves in shadow roots), no dependencies, Locker Service safe.
7
+ *
8
+ * Keyboard users have equivalent paths via the WAI-ARIA grid navigation
9
+ * (arrow keys + Enter) that every view already implements.
10
+ */
11
+
12
+ const DRAG_THRESHOLD_PX = 4;
13
+ const SNAP_MINUTES = 15;
14
+ const PX_PER_MINUTE = 1; // time grids render 60px per hour
15
+
16
+ /** Snap a minute offset to the grid. Exported for tests. */
17
+ export function snapMinutes(minutes, snap = SNAP_MINUTES) {
18
+ return Math.round(minutes / snap) * snap;
19
+ }
20
+
21
+ /** Clamp a start minute so [start, start+duration] stays inside a day. */
22
+ export function clampStartMinutes(startMinutes, durationMinutes) {
23
+ return Math.max(0, Math.min(startMinutes, 24 * 60 - durationMinutes));
24
+ }
25
+
26
+ /**
27
+ * Combine a target day with an original start's time-of-day.
28
+ * Exported for tests.
29
+ */
30
+ export function moveDatePreservingTime(targetDay, originalStart) {
31
+ const next = new Date(targetDay);
32
+ next.setHours(
33
+ originalStart.getHours(),
34
+ originalStart.getMinutes(),
35
+ originalStart.getSeconds(),
36
+ originalStart.getMilliseconds()
37
+ );
38
+ return next;
39
+ }
40
+
41
+ export class DragController {
42
+ constructor(renderer) {
43
+ this.renderer = renderer;
44
+ this.container = renderer.container;
45
+ this.stateManager = renderer.stateManager;
46
+ this._active = null;
47
+ this._docListeners = [];
48
+ }
49
+
50
+ /** Month view: drag an event chip onto another day cell. */
51
+ enableMonthMove() {
52
+ this.renderer.addListener(this.container, 'pointerdown', e => {
53
+ if (e.button !== 0) return;
54
+ const eventEl = e.target.closest('.fc-event');
55
+ if (!eventEl || !this.container.contains(eventEl)) return;
56
+ const originCell = eventEl.closest('.fc-month-day');
57
+ if (!originCell) return;
58
+
59
+ this._arm(e, {
60
+ mode: 'month-move',
61
+ eventEl,
62
+ eventId: eventEl.dataset.eventId,
63
+ onDragMove: ev => this._monthDragMove(ev),
64
+ onDrop: ev => this._monthDrop(ev)
65
+ });
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Time grids (week/day): drag events vertically/across columns to move,
71
+ * drag the bottom handle to resize, drag empty grid to create.
72
+ */
73
+ enableTimeGrid(columnSelector) {
74
+ this._columnSelector = columnSelector;
75
+ this._injectResizeHandles();
76
+
77
+ this.renderer.addListener(this.container, 'pointerdown', e => {
78
+ if (e.button !== 0) return;
79
+
80
+ const handle = e.target.closest('.fc-resize-handle');
81
+ if (handle) {
82
+ const eventEl = handle.closest('.fc-timed-event');
83
+ if (!eventEl) return;
84
+ e.preventDefault();
85
+ this._arm(e, {
86
+ mode: 'resize',
87
+ eventEl,
88
+ eventId: eventEl.dataset.eventId,
89
+ originTop: parseFloat(eventEl.style.top) || 0,
90
+ originHeight: parseFloat(eventEl.style.height) || 30,
91
+ onDragMove: ev => this._resizeDragMove(ev),
92
+ onDrop: () => this._resizeDrop()
93
+ });
94
+ return;
95
+ }
96
+
97
+ const eventEl = e.target.closest('.fc-timed-event');
98
+ if (eventEl && this.container.contains(eventEl)) {
99
+ this._arm(e, {
100
+ mode: 'time-move',
101
+ eventEl,
102
+ eventId: eventEl.dataset.eventId,
103
+ originTop: parseFloat(eventEl.style.top) || 0,
104
+ originColumn: eventEl.closest(columnSelector),
105
+ onDragMove: ev => this._timeMoveDragMove(ev),
106
+ onDrop: () => this._timeMoveDrop()
107
+ });
108
+ return;
109
+ }
110
+
111
+ const column = e.target.closest(columnSelector);
112
+ if (column && !e.target.closest('.fc-event')) {
113
+ this._arm(e, {
114
+ mode: 'create',
115
+ column,
116
+ onDragMove: ev => this._createDragMove(ev),
117
+ onDrop: () => this._createDrop()
118
+ });
119
+ }
120
+ });
121
+ }
122
+
123
+ /** Track from pointerdown; promote to a drag past the threshold. */
124
+ _arm(e, spec) {
125
+ this._active = {
126
+ ...spec,
127
+ startX: e.clientX,
128
+ startY: e.clientY,
129
+ dragging: false
130
+ };
131
+ const move = ev => this._onPointerMove(ev);
132
+ const up = ev => this._onPointerUp(ev);
133
+ const cancel = () => this._cancel();
134
+ const key = ev => {
135
+ if (ev.key === 'Escape') this._cancel();
136
+ };
137
+ const doc = this.container.ownerDocument;
138
+ doc.addEventListener('pointermove', move);
139
+ doc.addEventListener('pointerup', up);
140
+ doc.addEventListener('pointercancel', cancel);
141
+ doc.addEventListener('keydown', key);
142
+ this._docListeners = [
143
+ ['pointermove', move],
144
+ ['pointerup', up],
145
+ ['pointercancel', cancel],
146
+ ['keydown', key]
147
+ ];
148
+ }
149
+
150
+ _onPointerMove(e) {
151
+ const a = this._active;
152
+ if (!a) return;
153
+ if (!a.dragging) {
154
+ if (
155
+ Math.abs(e.clientX - a.startX) < DRAG_THRESHOLD_PX &&
156
+ Math.abs(e.clientY - a.startY) < DRAG_THRESHOLD_PX
157
+ ) {
158
+ return;
159
+ }
160
+ a.dragging = true;
161
+ if (a.eventEl) {
162
+ a.eventEl.classList.add('fc-dragging');
163
+ }
164
+ }
165
+ e.preventDefault();
166
+ a.onDragMove(e);
167
+ }
168
+
169
+ _onPointerUp(e) {
170
+ const a = this._active;
171
+ this._teardownDocListeners();
172
+ if (!a) return;
173
+ if (a.dragging) {
174
+ // Swallow the click that follows a drag so it doesn't select/open
175
+ const doc = this.container.ownerDocument;
176
+ const swallow = ev => {
177
+ ev.stopPropagation();
178
+ ev.preventDefault();
179
+ };
180
+ doc.addEventListener('click', swallow, { capture: true, once: true });
181
+ setTimeout(() => doc.removeEventListener('click', swallow, { capture: true }), 0);
182
+ a.onDrop(e);
183
+ }
184
+ this._cleanupVisuals(a);
185
+ this._active = null;
186
+ }
187
+
188
+ _cancel() {
189
+ const a = this._active;
190
+ this._teardownDocListeners();
191
+ this._active = null;
192
+ if (a) this._cleanupVisuals(a);
193
+ }
194
+
195
+ _teardownDocListeners() {
196
+ const doc = this.container.ownerDocument;
197
+ for (const [name, fn] of this._docListeners) {
198
+ doc.removeEventListener(name, fn);
199
+ }
200
+ this._docListeners = [];
201
+ }
202
+
203
+ _cleanupVisuals(a) {
204
+ if (a.eventEl) {
205
+ a.eventEl.classList.remove('fc-dragging');
206
+ a.eventEl.style.transform = '';
207
+ }
208
+ this.container
209
+ .querySelectorAll('.fc-drop-target')
210
+ .forEach(el => el.classList.remove('fc-drop-target'));
211
+ this._selectionEl?.remove();
212
+ this._selectionEl = null;
213
+ }
214
+
215
+ // ----- month move -----
216
+
217
+ _cellAtPoint(x, y) {
218
+ for (const cell of this.container.querySelectorAll('.fc-month-day')) {
219
+ const r = cell.getBoundingClientRect();
220
+ if (x >= r.left && x < r.right && y >= r.top && y < r.bottom) return cell;
221
+ }
222
+ return null;
223
+ }
224
+
225
+ _monthDragMove(e) {
226
+ const a = this._active;
227
+ a.eventEl.style.transform = `translate(${e.clientX - a.startX}px, ${e.clientY - a.startY}px)`;
228
+ const cell = this._cellAtPoint(e.clientX, e.clientY);
229
+ if (cell !== a.dropCell) {
230
+ a.dropCell?.classList.remove('fc-drop-target');
231
+ cell?.classList.add('fc-drop-target');
232
+ a.dropCell = cell;
233
+ }
234
+ }
235
+
236
+ _monthDrop() {
237
+ const a = this._active ?? {};
238
+ const cell = a.dropCell;
239
+ if (!cell) return;
240
+ const event = this.stateManager.getEvents().find(ev => ev.id === a.eventId);
241
+ if (!event) return;
242
+ const oldStart = new Date(event.start);
243
+ const newStart = moveDatePreservingTime(new Date(cell.dataset.date), oldStart);
244
+ const delta = newStart.getTime() - oldStart.getTime();
245
+ if (delta === 0) return;
246
+ this.stateManager.updateEvent(a.eventId, {
247
+ start: newStart,
248
+ end: new Date(new Date(event.end).getTime() + delta)
249
+ });
250
+ }
251
+
252
+ // ----- time-grid move -----
253
+
254
+ _columnAtPoint(x) {
255
+ for (const col of this.container.querySelectorAll(this._columnSelector)) {
256
+ const r = col.getBoundingClientRect();
257
+ if (x >= r.left && x < r.right) return col;
258
+ }
259
+ return null;
260
+ }
261
+
262
+ _timeMoveDragMove(e) {
263
+ const a = this._active;
264
+ const col = this._columnAtPoint(e.clientX) || a.originColumn;
265
+ a.dropColumn = col;
266
+ a.deltaMinutes = snapMinutes((e.clientY - a.startY) / PX_PER_MINUTE);
267
+ const originRect = a.originColumn.getBoundingClientRect();
268
+ const colRect = col.getBoundingClientRect();
269
+ a.eventEl.style.transform = `translate(${colRect.left - originRect.left}px, ${a.deltaMinutes * PX_PER_MINUTE}px)`;
270
+ }
271
+
272
+ _timeMoveDrop() {
273
+ const a = this._active ?? {};
274
+ const event = this.stateManager.getEvents().find(ev => ev.id === a.eventId);
275
+ if (!event || (!a.deltaMinutes && a.dropColumn === a.originColumn)) return;
276
+
277
+ const oldStart = new Date(event.start);
278
+ const duration = new Date(event.end).getTime() - oldStart.getTime();
279
+ const targetDay = a.dropColumn ? new Date(a.dropColumn.dataset.date) : oldStart;
280
+ const dayAligned = moveDatePreservingTime(targetDay, oldStart);
281
+ const startMinutes = clampStartMinutes(
282
+ dayAligned.getHours() * 60 + dayAligned.getMinutes() + (a.deltaMinutes || 0),
283
+ Math.round(duration / 60000)
284
+ );
285
+ const newStart = new Date(dayAligned);
286
+ newStart.setHours(Math.floor(startMinutes / 60), startMinutes % 60, 0, 0);
287
+ if (newStart.getTime() === oldStart.getTime()) return;
288
+ this.stateManager.updateEvent(a.eventId, {
289
+ start: newStart,
290
+ end: new Date(newStart.getTime() + duration)
291
+ });
292
+ }
293
+
294
+ // ----- resize -----
295
+
296
+ _resizeDragMove(e) {
297
+ const a = this._active;
298
+ const deltaMinutes = snapMinutes((e.clientY - a.startY) / PX_PER_MINUTE);
299
+ a.newHeight = Math.max(SNAP_MINUTES, a.originHeight + deltaMinutes);
300
+ a.newHeight = Math.min(a.newHeight, 24 * 60 - a.originTop);
301
+ a.eventEl.style.height = `${a.newHeight}px`;
302
+ }
303
+
304
+ _resizeDrop() {
305
+ const a = this._active ?? {};
306
+ if (!a.newHeight || a.newHeight === a.originHeight) return;
307
+ const event = this.stateManager.getEvents().find(ev => ev.id === a.eventId);
308
+ if (!event) return;
309
+ const newEnd = new Date(new Date(event.start).getTime() + a.newHeight * 60000);
310
+ this.stateManager.updateEvent(a.eventId, { end: newEnd });
311
+ }
312
+
313
+ // ----- drag-to-create -----
314
+
315
+ _createDragMove(e) {
316
+ const a = this._active;
317
+ const colRect = a.column.getBoundingClientRect();
318
+ const fromY = a.startY - colRect.top;
319
+ const toY = e.clientY - colRect.top;
320
+ a.fromMinutes = snapMinutes(Math.min(fromY, toY) / PX_PER_MINUTE);
321
+ a.toMinutes = snapMinutes(Math.max(fromY, toY) / PX_PER_MINUTE);
322
+ a.fromMinutes = Math.max(0, a.fromMinutes);
323
+ a.toMinutes = Math.min(24 * 60, Math.max(a.toMinutes, a.fromMinutes + SNAP_MINUTES));
324
+
325
+ if (!this._selectionEl) {
326
+ this._selectionEl = this.container.ownerDocument.createElement('div');
327
+ this._selectionEl.className = 'fc-drag-selection';
328
+ a.column.appendChild(this._selectionEl);
329
+ }
330
+ this._selectionEl.style.top = `${a.fromMinutes * PX_PER_MINUTE}px`;
331
+ this._selectionEl.style.height = `${(a.toMinutes - a.fromMinutes) * PX_PER_MINUTE}px`;
332
+ }
333
+
334
+ _createDrop() {
335
+ const a = this._active ?? {};
336
+ if (a.fromMinutes == null) return;
337
+ const start = new Date(a.column.dataset.date);
338
+ start.setHours(Math.floor(a.fromMinutes / 60), a.fromMinutes % 60, 0, 0);
339
+ const end = new Date(a.column.dataset.date);
340
+ end.setHours(Math.floor(a.toMinutes / 60), a.toMinutes % 60, 0, 0);
341
+ this.container.dispatchEvent(
342
+ new CustomEvent('range-select', {
343
+ detail: { start, end },
344
+ bubbles: true,
345
+ composed: true
346
+ })
347
+ );
348
+ }
349
+
350
+ /** Append a resize handle to every timed event (idempotent per render). */
351
+ _injectResizeHandles() {
352
+ for (const el of this.container.querySelectorAll('.fc-timed-event')) {
353
+ if (el.querySelector('.fc-resize-handle')) continue;
354
+ const handle = this.container.ownerDocument.createElement('div');
355
+ handle.className = 'fc-resize-handle';
356
+ handle.setAttribute('aria-hidden', 'true');
357
+ el.appendChild(handle);
358
+ }
359
+ }
360
+ }
361
+
362
+ export default DragController;
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { BaseViewRenderer } from './BaseViewRenderer.js';
8
+ import { DragController } from '../core/DragController.js';
8
9
  import { DateUtils } from '../utils/DateUtils.js';
9
10
 
10
11
  export class DayViewRenderer extends BaseViewRenderer {
@@ -216,6 +217,9 @@ export class DayViewRenderer extends BaseViewRenderer {
216
217
  const current = this.stateManager.getState().currentDate || new Date();
217
218
  const label = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }).format(current);
218
219
  this._enhanceTimeGridAccessibility('.fc-day-column', label);
220
+
221
+ // Drag to move/resize events and drag empty grid to create
222
+ new DragController(this).enableTimeGrid('.fc-day-column');
219
223
  }
220
224
 
221
225
  _scrollToCurrentTime() {
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { BaseViewRenderer } from './BaseViewRenderer.js';
8
8
  import { DateUtils } from '../utils/DateUtils.js';
9
+ import { DragController } from '../core/DragController.js';
9
10
 
10
11
  export class MonthViewRenderer extends BaseViewRenderer {
11
12
  constructor(container, stateManager) {
@@ -228,6 +229,9 @@ export class MonthViewRenderer extends BaseViewRenderer {
228
229
 
229
230
  // Common event handlers (event clicks)
230
231
  this.attachCommonEventHandlers();
232
+
233
+ // Drag an event chip onto another day to move it
234
+ new DragController(this).enableMonthMove();
231
235
  }
232
236
 
233
237
  /**
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { BaseViewRenderer } from './BaseViewRenderer.js';
8
8
  import { DateUtils } from '../utils/DateUtils.js';
9
+ import { DragController } from '../core/DragController.js';
9
10
 
10
11
  export class WeekViewRenderer extends BaseViewRenderer {
11
12
  constructor(container, stateManager) {
@@ -196,6 +197,9 @@ export class WeekViewRenderer extends BaseViewRenderer {
196
197
  const first = days[0] ? new Date(days[0].date) : new Date();
197
198
  const label = `Week of ${new Intl.DateTimeFormat(locale, { month: 'long', day: 'numeric', year: 'numeric' }).format(first)}`;
198
199
  this._enhanceTimeGridAccessibility('.fc-week-day-column', label);
200
+
201
+ // Drag to move/resize events and drag empty grid to create
202
+ new DragController(this).enableTimeGrid('.fc-week-day-column');
199
203
  }
200
204
 
201
205
  _scrollToCurrentTime() {
@@ -2,6 +2,34 @@
2
2
  * StyleUtils - Styling utilities and theme management
3
3
  */
4
4
 
5
+ /**
6
+ * Named theme presets applied via the <forcecal-main theme="..."> attribute.
7
+ * Each maps design tokens to a platform's visual language.
8
+ */
9
+ export const THEME_PRESETS = {
10
+ // Salesforce Lightning Design System: looks native on the platform
11
+ slds: {
12
+ '--fc-primary-color': '#0176d3',
13
+ '--fc-primary-hover': '#014486',
14
+ '--fc-primary-light': '#eef4ff',
15
+ '--fc-accent-color': '#0b5cab',
16
+ '--fc-text-color': '#181818',
17
+ '--fc-text-secondary': '#706e6b',
18
+ '--fc-text-light': '#939393',
19
+ '--fc-border-color': '#e5e5e5',
20
+ '--fc-border-color-hover': '#c9c9c9',
21
+ '--fc-background': '#ffffff',
22
+ '--fc-background-alt': '#f3f3f3',
23
+ '--fc-background-hover': '#f3f3f3',
24
+ '--fc-background-active': '#d8e6fe',
25
+ '--fc-danger-color': '#ea001e',
26
+ '--fc-success-color': '#2e844a',
27
+ '--fc-border-radius': '0.25rem',
28
+ '--fc-border-radius-sm': '0.125rem',
29
+ '--fc-font-family': "'Salesforce Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
30
+ }
31
+ };
32
+
5
33
  export class StyleUtils {
6
34
  /**
7
35
  * Default theme colors
@@ -59,6 +59,12 @@ export declare class ForceCalendar extends BaseComponent {
59
59
  getStyles(): string;
60
60
  template(): string;
61
61
  renderView(): string;
62
+ /**
63
+ * Apply a named theme preset (e.g. theme="slds") as host-level custom
64
+ * properties so it cascades into the shadow DOM and stays overridable
65
+ * by page-level --fc-* variables.
66
+ */
67
+ _applyTheme(name: any): void;
62
68
  afterRender(): void;
63
69
  handleNavigation(event: any): void;
64
70
  handleViewChange(event: any): void;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * DragController - pointer-driven interactions for calendar views.
3
+ *
4
+ * Implements drag-to-move (month and time grids), drag-to-resize, and
5
+ * drag-to-create using Pointer Events only: no HTML5 drag-and-drop (which
6
+ * misbehaves in shadow roots), no dependencies, Locker Service safe.
7
+ *
8
+ * Keyboard users have equivalent paths via the WAI-ARIA grid navigation
9
+ * (arrow keys + Enter) that every view already implements.
10
+ */
11
+ /** Snap a minute offset to the grid. Exported for tests. */
12
+ export declare function snapMinutes(minutes: any, snap?: number): number;
13
+ /** Clamp a start minute so [start, start+duration] stays inside a day. */
14
+ export declare function clampStartMinutes(startMinutes: any, durationMinutes: any): number;
15
+ /**
16
+ * Combine a target day with an original start's time-of-day.
17
+ * Exported for tests.
18
+ */
19
+ export declare function moveDatePreservingTime(targetDay: any, originalStart: any): Date;
20
+ export declare class DragController {
21
+ renderer: any;
22
+ container: any;
23
+ stateManager: any;
24
+ _active: any;
25
+ _docListeners: any[];
26
+ _columnSelector: any;
27
+ _selectionEl: any;
28
+ constructor(renderer: any);
29
+ /** Month view: drag an event chip onto another day cell. */
30
+ enableMonthMove(): void;
31
+ /**
32
+ * Time grids (week/day): drag events vertically/across columns to move,
33
+ * drag the bottom handle to resize, drag empty grid to create.
34
+ */
35
+ enableTimeGrid(columnSelector: any): void;
36
+ /** Track from pointerdown; promote to a drag past the threshold. */
37
+ _arm(e: any, spec: any): void;
38
+ _onPointerMove(e: any): void;
39
+ _onPointerUp(e: any): void;
40
+ _cancel(): void;
41
+ _teardownDocListeners(): void;
42
+ _cleanupVisuals(a: any): void;
43
+ _cellAtPoint(x: any, y: any): any;
44
+ _monthDragMove(e: any): void;
45
+ _monthDrop(): void;
46
+ _columnAtPoint(x: any): any;
47
+ _timeMoveDragMove(e: any): void;
48
+ _timeMoveDrop(): void;
49
+ _resizeDragMove(e: any): void;
50
+ _resizeDrop(): void;
51
+ _createDragMove(e: any): void;
52
+ _createDrop(): void;
53
+ /** Append a resize handle to every timed event (idempotent per render). */
54
+ _injectResizeHandles(): void;
55
+ }
56
+ export default DragController;
@@ -1,6 +1,32 @@
1
1
  /**
2
2
  * StyleUtils - Styling utilities and theme management
3
3
  */
4
+ /**
5
+ * Named theme presets applied via the <forcecal-main theme="..."> attribute.
6
+ * Each maps design tokens to a platform's visual language.
7
+ */
8
+ export declare const THEME_PRESETS: {
9
+ slds: {
10
+ '--fc-primary-color': string;
11
+ '--fc-primary-hover': string;
12
+ '--fc-primary-light': string;
13
+ '--fc-accent-color': string;
14
+ '--fc-text-color': string;
15
+ '--fc-text-secondary': string;
16
+ '--fc-text-light': string;
17
+ '--fc-border-color': string;
18
+ '--fc-border-color-hover': string;
19
+ '--fc-background': string;
20
+ '--fc-background-alt': string;
21
+ '--fc-background-hover': string;
22
+ '--fc-background-active': string;
23
+ '--fc-danger-color': string;
24
+ '--fc-success-color': string;
25
+ '--fc-border-radius': string;
26
+ '--fc-border-radius-sm': string;
27
+ '--fc-font-family': string;
28
+ };
29
+ };
4
30
  export declare class StyleUtils {
5
31
  /**
6
32
  * Default theme colors