@forcecalendar/interface 1.0.39 → 1.0.41
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/dist/force-calendar-interface.esm.js +208 -158
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js +33 -33
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/renderers/BaseViewRenderer.js +88 -4
- package/src/renderers/DayViewRenderer.js +5 -1
- package/src/renderers/WeekViewRenderer.js +5 -1
package/package.json
CHANGED
|
@@ -161,14 +161,86 @@ export class BaseViewRenderer {
|
|
|
161
161
|
return `<div class="fc-now-indicator" style="position: absolute; left: 0; right: 0; top: ${minutes}px; height: 2px; background: #dc2626; z-index: 15; pointer-events: none;"></div>`;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Compute overlap layout columns for a list of timed events.
|
|
166
|
+
* Returns a Map of event.id -> { column, totalColumns }.
|
|
167
|
+
* Uses a greedy left-to-right column packing algorithm.
|
|
168
|
+
* @param {Array} events - Array of event objects with start/end dates
|
|
169
|
+
* @returns {Map<string, {column: number, totalColumns: number}>}
|
|
170
|
+
*/
|
|
171
|
+
computeOverlapLayout(events) {
|
|
172
|
+
if (!events || events.length === 0) return new Map();
|
|
173
|
+
|
|
174
|
+
// Convert to sortable entries with minute ranges
|
|
175
|
+
const entries = events.map(evt => {
|
|
176
|
+
const start = new Date(evt.start);
|
|
177
|
+
const end = new Date(evt.end);
|
|
178
|
+
const startMin = start.getHours() * 60 + start.getMinutes();
|
|
179
|
+
const endMin = Math.max(startMin + 1, end.getHours() * 60 + end.getMinutes());
|
|
180
|
+
return { id: evt.id, startMin, endMin };
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Sort by start time, then by longer duration first
|
|
184
|
+
entries.sort((a, b) => a.startMin - b.startMin || (b.endMin - b.startMin) - (a.endMin - a.startMin));
|
|
185
|
+
|
|
186
|
+
// Assign columns greedily
|
|
187
|
+
const columns = []; // each column tracks the end time of its last event
|
|
188
|
+
const layout = new Map();
|
|
189
|
+
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
let placed = false;
|
|
192
|
+
for (let col = 0; col < columns.length; col++) {
|
|
193
|
+
if (columns[col] <= entry.startMin) {
|
|
194
|
+
columns[col] = entry.endMin;
|
|
195
|
+
layout.set(entry.id, { column: col, totalColumns: 0 });
|
|
196
|
+
placed = true;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!placed) {
|
|
201
|
+
layout.set(entry.id, { column: columns.length, totalColumns: 0 });
|
|
202
|
+
columns.push(entry.endMin);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Determine the max overlapping columns for each cluster of overlapping events
|
|
207
|
+
// Walk through entries and find connected groups
|
|
208
|
+
const groups = [];
|
|
209
|
+
let currentGroup = [];
|
|
210
|
+
let groupEnd = 0;
|
|
211
|
+
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
if (currentGroup.length === 0 || entry.startMin < groupEnd) {
|
|
214
|
+
currentGroup.push(entry);
|
|
215
|
+
groupEnd = Math.max(groupEnd, entry.endMin);
|
|
216
|
+
} else {
|
|
217
|
+
groups.push(currentGroup);
|
|
218
|
+
currentGroup = [entry];
|
|
219
|
+
groupEnd = entry.endMin;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (currentGroup.length > 0) groups.push(currentGroup);
|
|
223
|
+
|
|
224
|
+
for (const group of groups) {
|
|
225
|
+
const maxCol = Math.max(...group.map(e => layout.get(e.id).column)) + 1;
|
|
226
|
+
for (const entry of group) {
|
|
227
|
+
layout.get(entry.id).totalColumns = maxCol;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return layout;
|
|
232
|
+
}
|
|
233
|
+
|
|
164
234
|
/**
|
|
165
235
|
* Render a timed event block
|
|
166
236
|
* @param {Object} event - Event object
|
|
167
237
|
* @param {Object} options - Rendering options
|
|
238
|
+
* @param {Object} options.compact - Use compact layout
|
|
239
|
+
* @param {Object} options.overlapLayout - Map from computeOverlapLayout()
|
|
168
240
|
* @returns {string} HTML string
|
|
169
241
|
*/
|
|
170
242
|
renderTimedEvent(event, options = {}) {
|
|
171
|
-
const { compact = true } = options;
|
|
243
|
+
const { compact = true, overlapLayout = null } = options;
|
|
172
244
|
const start = new Date(event.start);
|
|
173
245
|
const end = new Date(event.end);
|
|
174
246
|
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
@@ -177,14 +249,26 @@ export class BaseViewRenderer {
|
|
|
177
249
|
|
|
178
250
|
const padding = compact ? '4px 8px' : '8px 12px';
|
|
179
251
|
const fontSize = compact ? '11px' : '13px';
|
|
180
|
-
const
|
|
181
|
-
const
|
|
252
|
+
const baseMargin = compact ? 2 : 12;
|
|
253
|
+
const rightPad = compact ? 2 : 24;
|
|
182
254
|
const borderRadius = compact ? '4px' : '6px';
|
|
183
255
|
|
|
256
|
+
// Compute left/width based on overlap columns
|
|
257
|
+
let leftPx, widthCalc;
|
|
258
|
+
if (overlapLayout && overlapLayout.has(event.id)) {
|
|
259
|
+
const { column, totalColumns } = overlapLayout.get(event.id);
|
|
260
|
+
const colWidth = `(100% - ${baseMargin + rightPad}px)`;
|
|
261
|
+
leftPx = `calc(${baseMargin}px + ${column} * ${colWidth} / ${totalColumns})`;
|
|
262
|
+
widthCalc = `calc(${colWidth} / ${totalColumns})`;
|
|
263
|
+
} else {
|
|
264
|
+
leftPx = `${baseMargin}px`;
|
|
265
|
+
widthCalc = `calc(100% - ${baseMargin + rightPad}px)`;
|
|
266
|
+
}
|
|
267
|
+
|
|
184
268
|
return `
|
|
185
269
|
<div class="fc-event fc-timed-event" data-event-id="${this.escapeHTML(event.id)}"
|
|
186
270
|
style="position: absolute; top: ${startMinutes}px; height: ${durationMinutes}px;
|
|
187
|
-
left: ${
|
|
271
|
+
left: ${leftPx}; width: ${widthCalc};
|
|
188
272
|
background-color: ${color}; border-radius: ${borderRadius};
|
|
189
273
|
padding: ${padding}; font-size: ${fontSize};
|
|
190
274
|
font-weight: 500; color: white; overflow: hidden;
|
|
@@ -24,6 +24,7 @@ export class DayViewRenderer extends BaseViewRenderer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
this.cleanup();
|
|
27
|
+
this._scrolled = false;
|
|
27
28
|
const config = this.stateManager.getState().config;
|
|
28
29
|
const html = this._renderDayView(viewData, config);
|
|
29
30
|
this.container.innerHTML = html;
|
|
@@ -168,7 +169,10 @@ export class DayViewRenderer extends BaseViewRenderer {
|
|
|
168
169
|
${isToday ? this.renderNowIndicator() : ''}
|
|
169
170
|
|
|
170
171
|
<!-- Timed events -->
|
|
171
|
-
${
|
|
172
|
+
${(() => {
|
|
173
|
+
const layout = this.computeOverlapLayout(timedEvents);
|
|
174
|
+
return timedEvents.map(evt => this.renderTimedEvent(evt, { compact: false, overlapLayout: layout })).join('');
|
|
175
|
+
})()}
|
|
172
176
|
</div>
|
|
173
177
|
`;
|
|
174
178
|
}
|
|
@@ -24,6 +24,7 @@ export class WeekViewRenderer extends BaseViewRenderer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
this.cleanup();
|
|
27
|
+
this._scrolled = false;
|
|
27
28
|
const config = this.stateManager.getState().config;
|
|
28
29
|
const html = this._renderWeekView(viewData, config);
|
|
29
30
|
this.container.innerHTML = html;
|
|
@@ -147,7 +148,10 @@ export class WeekViewRenderer extends BaseViewRenderer {
|
|
|
147
148
|
${day.isToday ? this.renderNowIndicator() : ''}
|
|
148
149
|
|
|
149
150
|
<!-- Timed events -->
|
|
150
|
-
${
|
|
151
|
+
${(() => {
|
|
152
|
+
const layout = this.computeOverlapLayout(day.timedEvents);
|
|
153
|
+
return day.timedEvents.map(evt => this.renderTimedEvent(evt, { compact: true, overlapLayout: layout })).join('');
|
|
154
|
+
})()}
|
|
151
155
|
</div>
|
|
152
156
|
`;
|
|
153
157
|
}
|