@dayflow/plugin-sidebar 1.2.1 → 1.2.2

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/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AudioLines, useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, ContentSlot, MiniCalendar, ContextMenu, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, parseICS, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
1
+ import { ChevronDown, AudioLines, useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, ContentSlot, MiniCalendar, ContextMenu, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, parseICS, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
2
2
  import { options, Fragment, h } from 'preact';
3
3
  import { useState, useRef, useCallback, useEffect, useMemo } from 'preact/hooks';
4
4
  import { hexToHsl, lightnessToSliderValue } from '@dayflow/blossom-color-picker';
@@ -44,21 +44,33 @@ const getCalendarInitials = (calendar) => {
44
44
  const name = calendar.name || calendar.id;
45
45
  return name.charAt(0).toUpperCase();
46
46
  };
47
+ const CalendarItem = ({ calendar, isDraggable, isEditable: _isEditable, editingId, editingName, setEditingName, editInputRef, draggedCalendarId, dropTarget, activeContextMenuCalendarId, onDragStart, onDragEnd, onDragOver, onDragLeave, onDrop, onContextMenu, onToggleVisibility, onRenameStart, onRenameSave, onRenameKeyDown, }) => {
48
+ var _a;
49
+ const isVisible = calendar.isVisible !== false;
50
+ const calendarColor = ((_a = calendar.colors) === null || _a === void 0 ? void 0 : _a.lineColor) || '#3b82f6';
51
+ const showIcon = Boolean(calendar.icon);
52
+ const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
53
+ const isActive = activeContextMenuCalendarId === calendar.id || editingId === calendar.id;
54
+ return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => onDragOver(e, calendar.id), onDragLeave: onDragLeave, onDrop: () => onDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => onDragStart(calendar, e), onDragEnd: onDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
55
+ '--checkbox-color': calendarColor,
56
+ }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: onRenameSave, onKeyDown: onRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => onRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
57
+ };
47
58
  const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onContextMenu, editingId, setEditingId, activeContextMenuCalendarId, isDraggable = true, isEditable = true, }) => {
59
+ var _a;
48
60
  const [editingName, setEditingName] = useState('');
49
61
  const editInputRef = useRef(null);
50
62
  const isProcessedRef = useRef(false);
51
63
  // Drag state
52
64
  const [draggedCalendarId, setDraggedCalendarId] = useState(null);
53
65
  const [dropTarget, setDropTarget] = useState(null);
66
+ // Collapsed sources state
67
+ const [collapsedSources, setCollapsedSources] = useState({});
54
68
  const handleDragStart = useCallback((calendar, e) => {
55
- // Prevent dragging when editing or not draggable
56
69
  if (editingId || !isDraggable) {
57
70
  e.preventDefault();
58
71
  return;
59
72
  }
60
73
  setDraggedCalendarId(calendar.id);
61
- // Store calendar data for drop handling
62
74
  const dragData = {
63
75
  calendarId: calendar.id,
64
76
  calendarName: calendar.name,
@@ -107,11 +119,9 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
107
119
  return;
108
120
  const fromIndex = calendars.findIndex(c => c.id === draggedCalendarId);
109
121
  let toIndex = calendars.findIndex(c => c.id === targetCalendar.id);
110
- // Adjust target index based on position
111
122
  if (dropTarget.position === 'bottom') {
112
123
  toIndex += 1;
113
124
  }
114
- // Adjust for removal of the item
115
125
  if (toIndex > fromIndex) {
116
126
  toIndex -= 1;
117
127
  }
@@ -169,24 +179,75 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
169
179
  }
170
180
  }
171
181
  }, [editingId, calendars]);
172
- return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: u("ul", { className: 'relative space-y-1', children: calendars.map(calendar => {
173
- var _a;
174
- const isVisible = calendar.isVisible !== false;
175
- const calendarColor = ((_a = calendar.colors) === null || _a === void 0 ? void 0 : _a.lineColor) || '#3b82f6';
176
- const showIcon = Boolean(calendar.icon);
177
- const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
178
- const isActive = activeContextMenuCalendarId === calendar.id ||
179
- editingId === calendar.id;
180
- return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
181
- '--checkbox-color': calendarColor,
182
- }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
183
- }) }) }));
182
+ const toggleSource = useCallback((source) => {
183
+ setCollapsedSources(prev => (Object.assign(Object.assign({}, prev), { [source]: !prev[source] })));
184
+ }, []);
185
+ // Shared item props (excludes per-item fields)
186
+ const sharedItemProps = {
187
+ isDraggable,
188
+ isEditable,
189
+ editingId,
190
+ editingName,
191
+ setEditingName,
192
+ editInputRef,
193
+ isProcessedRef,
194
+ draggedCalendarId,
195
+ dropTarget,
196
+ activeContextMenuCalendarId,
197
+ onDragStart: handleDragStart,
198
+ onDragEnd: handleDragEnd,
199
+ onDragOver: handleDragOver,
200
+ onDragLeave: handleDragLeave,
201
+ onDrop: handleDrop,
202
+ onContextMenu,
203
+ onToggleVisibility,
204
+ onRenameStart: handleRenameStart,
205
+ onRenameSave: handleRenameSave,
206
+ onRenameKeyDown: handleRenameKeyDown,
207
+ setEditingId,
208
+ };
209
+ // Check if any calendar has a source
210
+ const hasSources = calendars.some(c => c.source);
211
+ if (!hasSources) {
212
+ // Flat list (original behaviour)
213
+ return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: u("ul", { className: 'relative space-y-1', children: calendars.map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) }) }));
214
+ }
215
+ // Group calendars by source; calendars without a source go into a null group
216
+ const groups = new Map();
217
+ for (const calendar of calendars) {
218
+ const key = (_a = calendar.source) !== null && _a !== void 0 ? _a : null;
219
+ if (!groups.has(key))
220
+ groups.set(key, []);
221
+ groups.get(key).push(calendar);
222
+ }
223
+ return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: [groups.has(null) && (u("ul", { className: 'relative space-y-1', children: groups.get(null).map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) })), Array.from(groups.entries())
224
+ .filter(([source]) => source !== null)
225
+ .map(([source, groupCalendars]) => {
226
+ const isCollapsed = collapsedSources[source];
227
+ return (u("div", { className: 'mt-1', children: [u("button", { type: 'button', className: 'flex w-full items-center justify-between rounded px-2 py-1.5 text-xs font-medium text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-slate-800', onClick: () => toggleSource(source), children: [u("span", { className: 'truncate', children: source }), u(ChevronDown, { width: 13, height: 13, className: `ml-1 shrink-0 transition-transform duration-200 ${isCollapsed ? '-rotate-90' : ''}` })] }), u("div", { style: {
228
+ display: 'grid',
229
+ gridTemplateRows: isCollapsed ? '0fr' : '1fr',
230
+ transition: 'grid-template-rows 200ms ease',
231
+ }, children: u("div", { style: { overflow: 'hidden' }, children: u("ul", { className: 'relative space-y-1', children: groupCalendars.map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) }) }) })] }, source));
232
+ })] }));
184
233
  };
185
234
 
235
+ const CalendarChip = ({ name, color }) => (u("span", { className: 'mx-0.5 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium', style: { backgroundColor: `${color}26`, color }, children: name }));
236
+
237
+ const CAL_SENTINEL = '\u0001C\u0001';
238
+ function renderWithChip(template, name, color) {
239
+ return template
240
+ .split(CAL_SENTINEL)
241
+ .flatMap((part, i) => i === 0
242
+ ? [part]
243
+ : [u(CalendarChip, { name: name, color: color }, i), part]);
244
+ }
186
245
  const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
246
+ var _a, _b;
187
247
  const [showMergeDropdown, setShowMergeDropdown] = useState(false);
188
248
  const { t } = useLocale();
189
- return createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
249
+ const calendarColor = (_b = (_a = calendars.find(c => c.id === calendarId)) === null || _a === void 0 ? void 0 : _a.colors.lineColor) !== null && _b !== void 0 ? _b : '#6b7280';
250
+ return createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 flex flex-wrap items-center gap-y-0.5 text-sm text-gray-600 dark:text-gray-300', children: renderWithChip(t('deleteCalendarMessage', { calendarName: CAL_SENTINEL }), calendarName, calendarColor) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
190
251
  .filter(c => c.id !== calendarId)
191
252
  .map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
192
253
  onMergeSelect(calendar.id);
@@ -252,9 +313,29 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
252
313
  : (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
253
314
  };
254
315
 
255
- const MergeCalendarDialog = ({ sourceName, targetName, onConfirm, onCancel, }) => {
316
+ const SOURCE_SENTINEL = '\u0001S\u0001';
317
+ const TARGET_SENTINEL = '\u0001T\u0001';
318
+ function renderLine(line, source, target) {
319
+ return line
320
+ .split(new RegExp(`(${SOURCE_SENTINEL}|${TARGET_SENTINEL})`))
321
+ .map((part, i) => {
322
+ if (part === SOURCE_SENTINEL)
323
+ return u(CalendarChip, { name: source.name, color: source.color }, i);
324
+ if (part === TARGET_SENTINEL)
325
+ return u(CalendarChip, { name: target.name, color: target.color }, i);
326
+ return part;
327
+ });
328
+ }
329
+ const MergeCalendarDialog = ({ sourceName, sourceColor, targetName, targetColor, onConfirm, onCancel, }) => {
256
330
  const { t } = useLocale();
257
- return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('mergeConfirmMessage', { sourceName, targetName }) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
331
+ const source = { name: sourceName, color: sourceColor };
332
+ const target = { name: targetName, color: targetColor };
333
+ const messageTemplate = t('mergeConfirmMessage', {
334
+ sourceName: SOURCE_SENTINEL,
335
+ targetName: TARGET_SENTINEL,
336
+ });
337
+ const messageLines = messageTemplate.split('\n');
338
+ return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("div", { className: 'mt-3 space-y-1 text-sm text-gray-600 dark:text-gray-300', children: messageLines.map((line, i) => (u("p", { className: 'flex flex-wrap items-center gap-y-0.5', children: renderLine(line, source, target) }, i))) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
258
339
  };
259
340
 
260
341
  const stopPropagation = (e) => e.stopPropagation();
@@ -334,7 +415,7 @@ const SubscribeCalendarDialog = ({ onSubscribe, onCancel, }) => {
334
415
  };
335
416
 
336
417
  const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
337
- var _a, _b, _c, _d;
418
+ var _a, _b;
338
419
  const { t } = useLocale();
339
420
  // Detect if custom color picker slot is provided
340
421
  const hasCustomPicker = app.state.overrides.includes('colorPicker');
@@ -592,14 +673,18 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
592
673
  handleCloseContextMenu();
593
674
  }
594
675
  }, [contextMenu, calendars, app, handleCloseContextMenu]);
595
- const sourceCalendarName = mergeState
596
- ? ((_a = calendars.find(c => c.id === mergeState.sourceId)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown'
597
- : '';
598
- const targetCalendarName = mergeState
599
- ? ((_b = calendars.find(c => c.id === mergeState.targetId)) === null || _b === void 0 ? void 0 : _b.name) || 'Unknown'
600
- : '';
676
+ const sourceCalendar = mergeState
677
+ ? calendars.find(c => c.id === mergeState.sourceId)
678
+ : null;
679
+ const targetCalendar = mergeState
680
+ ? calendars.find(c => c.id === mergeState.targetId)
681
+ : null;
682
+ const sourceCalendarName = (sourceCalendar === null || sourceCalendar === void 0 ? void 0 : sourceCalendar.name) || 'Unknown';
683
+ const targetCalendarName = (targetCalendar === null || targetCalendar === void 0 ? void 0 : targetCalendar.name) || 'Unknown';
684
+ const sourceCalendarColor = (sourceCalendar === null || sourceCalendar === void 0 ? void 0 : sourceCalendar.colors.lineColor) || '#6b7280';
685
+ const targetCalendarColor = (targetCalendar === null || targetCalendar === void 0 ? void 0 : targetCalendar.colors.lineColor) || '#6b7280';
601
686
  const deleteCalendarName = deleteState
602
- ? ((_c = calendars.find(c => c.id === deleteState.calendarId)) === null || _c === void 0 ? void 0 : _c.name) || 'Unknown'
687
+ ? ((_a = calendars.find(c => c.id === deleteState.calendarId)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown'
603
688
  : '';
604
689
  const readOnlyConfig = app.getReadOnlyConfig();
605
690
  const isEditable = !app.state.readOnly;
@@ -637,7 +722,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
637
722
  }, editingId: editingCalendarId, setEditingId: setEditingCalendarId, activeContextMenuCalendarId: contextMenu === null || contextMenu === void 0 ? void 0 : contextMenu.calendarId, isDraggable: isDraggable, isEditable: isEditable }), u("div", { className: 'border-t border-gray-200 dark:border-slate-800', children: u(MiniCalendar, { visibleMonth: app.getVisibleMonth(), currentDate: app.getCurrentDate(), showHeader: true, onMonthChange: handleMonthChange, onDateSelect: date => app.setCurrentDate(date) }) })] })), contextMenu && (u(ContextMenu, { ref: contextMenuRef, x: contextMenu.x, y: contextMenu.y, onClose: handleCloseContextMenu, className: 'w-64 p-2', children: u(ContentSlot, { generatorName: 'calendarContextMenu', generatorArgs: {
638
723
  calendar: calendars.find(c => c.id === contextMenu.calendarId),
639
724
  onClose: handleCloseContextMenu,
640
- }, defaultContent: renderCalendarContextMenu ? (renderCalendarContextMenu(calendars.find(c => c.id === contextMenu.calendarId), handleCloseContextMenu)) : (u(Fragment, { children: [u(ContextMenuLabel, { children: t('calendarOptions') }), u(MergeMenuItem, { calendars: calendars, currentCalendarId: contextMenu.calendarId, onMergeSelect: handleMergeSelect }), u(ContextMenuItem, { onClick: handleDeleteCalendar, children: t('delete') }), u(ContextMenuItem, { onClick: handleExportCalendar, children: t('exportCalendar') || 'Export Calendar' }), u(ContextMenuSeparator, {}), u(ContextMenuColorPicker, { selectedColor: (_d = calendars.find(c => c.id === contextMenu.calendarId)) === null || _d === void 0 ? void 0 : _d.colors.lineColor, onSelect: handleColorSelect, onCustomColor: handleCustomColor })] })) }) })), sidebarContextMenu &&
725
+ }, defaultContent: renderCalendarContextMenu ? (renderCalendarContextMenu(calendars.find(c => c.id === contextMenu.calendarId), handleCloseContextMenu)) : (u(Fragment, { children: [u(ContextMenuLabel, { children: t('calendarOptions') }), u(MergeMenuItem, { calendars: calendars, currentCalendarId: contextMenu.calendarId, onMergeSelect: handleMergeSelect }), u(ContextMenuItem, { onClick: handleDeleteCalendar, children: t('delete') }), u(ContextMenuItem, { onClick: handleExportCalendar, children: t('exportCalendar') || 'Export Calendar' }), u(ContextMenuSeparator, {}), u(ContextMenuColorPicker, { selectedColor: (_b = calendars.find(c => c.id === contextMenu.calendarId)) === null || _b === void 0 ? void 0 : _b.colors.lineColor, onSelect: handleColorSelect, onCustomColor: handleCustomColor })] })) }) })), sidebarContextMenu &&
641
726
  createPortal(u(ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(ContextMenuItem, { onClick: () => {
642
727
  onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
643
728
  handleCloseSidebarContextMenu();
@@ -647,7 +732,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
647
732
  }, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
648
733
  createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body), subscribeDialogOpen &&
649
734
  createPortal(u(SubscribeCalendarDialog, { onSubscribe: handleSubscribeConfirm, onCancel: () => setSubscribeDialogOpen(false) }), document.body), mergeState &&
650
- createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
735
+ createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, sourceColor: sourceCalendarColor, targetName: targetCalendarName, targetColor: targetCalendarColor, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
651
736
  createPortal(u(DeleteCalendarDialog, { calendarId: deleteState.calendarId, calendarName: deleteCalendarName, calendars: calendars, step: deleteState.step, onStepChange: step => setDeleteState(prev => (prev ? Object.assign(Object.assign({}, prev), { step }) : null)), onConfirmDelete: handleConfirmDelete, onCancel: () => setDeleteState(null), onMergeSelect: handleDeleteMergeSelect }), document.body), customColorPicker &&
652
737
  createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
653
738
  app.updateCalendar(customColorPicker.calendarId, {});
package/dist/index.js CHANGED
@@ -46,21 +46,33 @@ const getCalendarInitials = (calendar) => {
46
46
  const name = calendar.name || calendar.id;
47
47
  return name.charAt(0).toUpperCase();
48
48
  };
49
+ const CalendarItem = ({ calendar, isDraggable, isEditable: _isEditable, editingId, editingName, setEditingName, editInputRef, draggedCalendarId, dropTarget, activeContextMenuCalendarId, onDragStart, onDragEnd, onDragOver, onDragLeave, onDrop, onContextMenu, onToggleVisibility, onRenameStart, onRenameSave, onRenameKeyDown, }) => {
50
+ var _a;
51
+ const isVisible = calendar.isVisible !== false;
52
+ const calendarColor = ((_a = calendar.colors) === null || _a === void 0 ? void 0 : _a.lineColor) || '#3b82f6';
53
+ const showIcon = Boolean(calendar.icon);
54
+ const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
55
+ const isActive = activeContextMenuCalendarId === calendar.id || editingId === calendar.id;
56
+ return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => onDragOver(e, calendar.id), onDragLeave: onDragLeave, onDrop: () => onDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => onDragStart(calendar, e), onDragEnd: onDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
57
+ '--checkbox-color': calendarColor,
58
+ }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: onRenameSave, onKeyDown: onRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(preact.Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => onRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(core.AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
59
+ };
49
60
  const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onContextMenu, editingId, setEditingId, activeContextMenuCalendarId, isDraggable = true, isEditable = true, }) => {
61
+ var _a;
50
62
  const [editingName, setEditingName] = hooks.useState('');
51
63
  const editInputRef = hooks.useRef(null);
52
64
  const isProcessedRef = hooks.useRef(false);
53
65
  // Drag state
54
66
  const [draggedCalendarId, setDraggedCalendarId] = hooks.useState(null);
55
67
  const [dropTarget, setDropTarget] = hooks.useState(null);
68
+ // Collapsed sources state
69
+ const [collapsedSources, setCollapsedSources] = hooks.useState({});
56
70
  const handleDragStart = hooks.useCallback((calendar, e) => {
57
- // Prevent dragging when editing or not draggable
58
71
  if (editingId || !isDraggable) {
59
72
  e.preventDefault();
60
73
  return;
61
74
  }
62
75
  setDraggedCalendarId(calendar.id);
63
- // Store calendar data for drop handling
64
76
  const dragData = {
65
77
  calendarId: calendar.id,
66
78
  calendarName: calendar.name,
@@ -109,11 +121,9 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
109
121
  return;
110
122
  const fromIndex = calendars.findIndex(c => c.id === draggedCalendarId);
111
123
  let toIndex = calendars.findIndex(c => c.id === targetCalendar.id);
112
- // Adjust target index based on position
113
124
  if (dropTarget.position === 'bottom') {
114
125
  toIndex += 1;
115
126
  }
116
- // Adjust for removal of the item
117
127
  if (toIndex > fromIndex) {
118
128
  toIndex -= 1;
119
129
  }
@@ -171,24 +181,75 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
171
181
  }
172
182
  }
173
183
  }, [editingId, calendars]);
174
- return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: u("ul", { className: 'relative space-y-1', children: calendars.map(calendar => {
175
- var _a;
176
- const isVisible = calendar.isVisible !== false;
177
- const calendarColor = ((_a = calendar.colors) === null || _a === void 0 ? void 0 : _a.lineColor) || '#3b82f6';
178
- const showIcon = Boolean(calendar.icon);
179
- const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
180
- const isActive = activeContextMenuCalendarId === calendar.id ||
181
- editingId === calendar.id;
182
- return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
183
- '--checkbox-color': calendarColor,
184
- }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(preact.Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(core.AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
185
- }) }) }));
184
+ const toggleSource = hooks.useCallback((source) => {
185
+ setCollapsedSources(prev => (Object.assign(Object.assign({}, prev), { [source]: !prev[source] })));
186
+ }, []);
187
+ // Shared item props (excludes per-item fields)
188
+ const sharedItemProps = {
189
+ isDraggable,
190
+ isEditable,
191
+ editingId,
192
+ editingName,
193
+ setEditingName,
194
+ editInputRef,
195
+ isProcessedRef,
196
+ draggedCalendarId,
197
+ dropTarget,
198
+ activeContextMenuCalendarId,
199
+ onDragStart: handleDragStart,
200
+ onDragEnd: handleDragEnd,
201
+ onDragOver: handleDragOver,
202
+ onDragLeave: handleDragLeave,
203
+ onDrop: handleDrop,
204
+ onContextMenu,
205
+ onToggleVisibility,
206
+ onRenameStart: handleRenameStart,
207
+ onRenameSave: handleRenameSave,
208
+ onRenameKeyDown: handleRenameKeyDown,
209
+ setEditingId,
210
+ };
211
+ // Check if any calendar has a source
212
+ const hasSources = calendars.some(c => c.source);
213
+ if (!hasSources) {
214
+ // Flat list (original behaviour)
215
+ return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: u("ul", { className: 'relative space-y-1', children: calendars.map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) }) }));
216
+ }
217
+ // Group calendars by source; calendars without a source go into a null group
218
+ const groups = new Map();
219
+ for (const calendar of calendars) {
220
+ const key = (_a = calendar.source) !== null && _a !== void 0 ? _a : null;
221
+ if (!groups.has(key))
222
+ groups.set(key, []);
223
+ groups.get(key).push(calendar);
224
+ }
225
+ return (u("div", { className: 'df-calendar-list flex-1 overflow-y-auto px-2 pb-3', children: [groups.has(null) && (u("ul", { className: 'relative space-y-1', children: groups.get(null).map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) })), Array.from(groups.entries())
226
+ .filter(([source]) => source !== null)
227
+ .map(([source, groupCalendars]) => {
228
+ const isCollapsed = collapsedSources[source];
229
+ return (u("div", { className: 'mt-1', children: [u("button", { type: 'button', className: 'flex w-full items-center justify-between rounded px-2 py-1.5 text-xs font-medium text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-slate-800', onClick: () => toggleSource(source), children: [u("span", { className: 'truncate', children: source }), u(core.ChevronDown, { width: 13, height: 13, className: `ml-1 shrink-0 transition-transform duration-200 ${isCollapsed ? '-rotate-90' : ''}` })] }), u("div", { style: {
230
+ display: 'grid',
231
+ gridTemplateRows: isCollapsed ? '0fr' : '1fr',
232
+ transition: 'grid-template-rows 200ms ease',
233
+ }, children: u("div", { style: { overflow: 'hidden' }, children: u("ul", { className: 'relative space-y-1', children: groupCalendars.map(calendar => (u(CalendarItem, Object.assign({ calendar: calendar }, sharedItemProps), calendar.id))) }) }) })] }, source));
234
+ })] }));
186
235
  };
187
236
 
237
+ const CalendarChip = ({ name, color }) => (u("span", { className: 'mx-0.5 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium', style: { backgroundColor: `${color}26`, color }, children: name }));
238
+
239
+ const CAL_SENTINEL = '\u0001C\u0001';
240
+ function renderWithChip(template, name, color) {
241
+ return template
242
+ .split(CAL_SENTINEL)
243
+ .flatMap((part, i) => i === 0
244
+ ? [part]
245
+ : [u(CalendarChip, { name: name, color: color }, i), part]);
246
+ }
188
247
  const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
248
+ var _a, _b;
189
249
  const [showMergeDropdown, setShowMergeDropdown] = hooks.useState(false);
190
250
  const { t } = core.useLocale();
191
- return core.createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(preact.Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
251
+ const calendarColor = (_b = (_a = calendars.find(c => c.id === calendarId)) === null || _a === void 0 ? void 0 : _a.colors.lineColor) !== null && _b !== void 0 ? _b : '#6b7280';
252
+ return core.createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(preact.Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 flex flex-wrap items-center gap-y-0.5 text-sm text-gray-600 dark:text-gray-300', children: renderWithChip(t('deleteCalendarMessage', { calendarName: CAL_SENTINEL }), calendarName, calendarColor) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
192
253
  .filter(c => c.id !== calendarId)
193
254
  .map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
194
255
  onMergeSelect(calendar.id);
@@ -254,9 +315,29 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
254
315
  : (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(core.ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
255
316
  };
256
317
 
257
- const MergeCalendarDialog = ({ sourceName, targetName, onConfirm, onCancel, }) => {
318
+ const SOURCE_SENTINEL = '\u0001S\u0001';
319
+ const TARGET_SENTINEL = '\u0001T\u0001';
320
+ function renderLine(line, source, target) {
321
+ return line
322
+ .split(new RegExp(`(${SOURCE_SENTINEL}|${TARGET_SENTINEL})`))
323
+ .map((part, i) => {
324
+ if (part === SOURCE_SENTINEL)
325
+ return u(CalendarChip, { name: source.name, color: source.color }, i);
326
+ if (part === TARGET_SENTINEL)
327
+ return u(CalendarChip, { name: target.name, color: target.color }, i);
328
+ return part;
329
+ });
330
+ }
331
+ const MergeCalendarDialog = ({ sourceName, sourceColor, targetName, targetColor, onConfirm, onCancel, }) => {
258
332
  const { t } = core.useLocale();
259
- return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('mergeConfirmMessage', { sourceName, targetName }) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
333
+ const source = { name: sourceName, color: sourceColor };
334
+ const target = { name: targetName, color: targetColor };
335
+ const messageTemplate = t('mergeConfirmMessage', {
336
+ sourceName: SOURCE_SENTINEL,
337
+ targetName: TARGET_SENTINEL,
338
+ });
339
+ const messageLines = messageTemplate.split('\n');
340
+ return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("div", { className: 'mt-3 space-y-1 text-sm text-gray-600 dark:text-gray-300', children: messageLines.map((line, i) => (u("p", { className: 'flex flex-wrap items-center gap-y-0.5', children: renderLine(line, source, target) }, i))) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
260
341
  };
261
342
 
262
343
  const stopPropagation = (e) => e.stopPropagation();
@@ -336,7 +417,7 @@ const SubscribeCalendarDialog = ({ onSubscribe, onCancel, }) => {
336
417
  };
337
418
 
338
419
  const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
339
- var _a, _b, _c, _d;
420
+ var _a, _b;
340
421
  const { t } = core.useLocale();
341
422
  // Detect if custom color picker slot is provided
342
423
  const hasCustomPicker = app.state.overrides.includes('colorPicker');
@@ -594,14 +675,18 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
594
675
  handleCloseContextMenu();
595
676
  }
596
677
  }, [contextMenu, calendars, app, handleCloseContextMenu]);
597
- const sourceCalendarName = mergeState
598
- ? ((_a = calendars.find(c => c.id === mergeState.sourceId)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown'
599
- : '';
600
- const targetCalendarName = mergeState
601
- ? ((_b = calendars.find(c => c.id === mergeState.targetId)) === null || _b === void 0 ? void 0 : _b.name) || 'Unknown'
602
- : '';
678
+ const sourceCalendar = mergeState
679
+ ? calendars.find(c => c.id === mergeState.sourceId)
680
+ : null;
681
+ const targetCalendar = mergeState
682
+ ? calendars.find(c => c.id === mergeState.targetId)
683
+ : null;
684
+ const sourceCalendarName = (sourceCalendar === null || sourceCalendar === void 0 ? void 0 : sourceCalendar.name) || 'Unknown';
685
+ const targetCalendarName = (targetCalendar === null || targetCalendar === void 0 ? void 0 : targetCalendar.name) || 'Unknown';
686
+ const sourceCalendarColor = (sourceCalendar === null || sourceCalendar === void 0 ? void 0 : sourceCalendar.colors.lineColor) || '#6b7280';
687
+ const targetCalendarColor = (targetCalendar === null || targetCalendar === void 0 ? void 0 : targetCalendar.colors.lineColor) || '#6b7280';
603
688
  const deleteCalendarName = deleteState
604
- ? ((_c = calendars.find(c => c.id === deleteState.calendarId)) === null || _c === void 0 ? void 0 : _c.name) || 'Unknown'
689
+ ? ((_a = calendars.find(c => c.id === deleteState.calendarId)) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown'
605
690
  : '';
606
691
  const readOnlyConfig = app.getReadOnlyConfig();
607
692
  const isEditable = !app.state.readOnly;
@@ -639,7 +724,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
639
724
  }, editingId: editingCalendarId, setEditingId: setEditingCalendarId, activeContextMenuCalendarId: contextMenu === null || contextMenu === void 0 ? void 0 : contextMenu.calendarId, isDraggable: isDraggable, isEditable: isEditable }), u("div", { className: 'border-t border-gray-200 dark:border-slate-800', children: u(core.MiniCalendar, { visibleMonth: app.getVisibleMonth(), currentDate: app.getCurrentDate(), showHeader: true, onMonthChange: handleMonthChange, onDateSelect: date => app.setCurrentDate(date) }) })] })), contextMenu && (u(core.ContextMenu, { ref: contextMenuRef, x: contextMenu.x, y: contextMenu.y, onClose: handleCloseContextMenu, className: 'w-64 p-2', children: u(core.ContentSlot, { generatorName: 'calendarContextMenu', generatorArgs: {
640
725
  calendar: calendars.find(c => c.id === contextMenu.calendarId),
641
726
  onClose: handleCloseContextMenu,
642
- }, defaultContent: renderCalendarContextMenu ? (renderCalendarContextMenu(calendars.find(c => c.id === contextMenu.calendarId), handleCloseContextMenu)) : (u(preact.Fragment, { children: [u(core.ContextMenuLabel, { children: t('calendarOptions') }), u(MergeMenuItem, { calendars: calendars, currentCalendarId: contextMenu.calendarId, onMergeSelect: handleMergeSelect }), u(core.ContextMenuItem, { onClick: handleDeleteCalendar, children: t('delete') }), u(core.ContextMenuItem, { onClick: handleExportCalendar, children: t('exportCalendar') || 'Export Calendar' }), u(core.ContextMenuSeparator, {}), u(core.ContextMenuColorPicker, { selectedColor: (_d = calendars.find(c => c.id === contextMenu.calendarId)) === null || _d === void 0 ? void 0 : _d.colors.lineColor, onSelect: handleColorSelect, onCustomColor: handleCustomColor })] })) }) })), sidebarContextMenu &&
727
+ }, defaultContent: renderCalendarContextMenu ? (renderCalendarContextMenu(calendars.find(c => c.id === contextMenu.calendarId), handleCloseContextMenu)) : (u(preact.Fragment, { children: [u(core.ContextMenuLabel, { children: t('calendarOptions') }), u(MergeMenuItem, { calendars: calendars, currentCalendarId: contextMenu.calendarId, onMergeSelect: handleMergeSelect }), u(core.ContextMenuItem, { onClick: handleDeleteCalendar, children: t('delete') }), u(core.ContextMenuItem, { onClick: handleExportCalendar, children: t('exportCalendar') || 'Export Calendar' }), u(core.ContextMenuSeparator, {}), u(core.ContextMenuColorPicker, { selectedColor: (_b = calendars.find(c => c.id === contextMenu.calendarId)) === null || _b === void 0 ? void 0 : _b.colors.lineColor, onSelect: handleColorSelect, onCustomColor: handleCustomColor })] })) }) })), sidebarContextMenu &&
643
728
  core.createPortal(u(core.ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(core.ContextMenuItem, { onClick: () => {
644
729
  onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
645
730
  handleCloseSidebarContextMenu();
@@ -649,7 +734,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
649
734
  }, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
650
735
  core.createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body), subscribeDialogOpen &&
651
736
  core.createPortal(u(SubscribeCalendarDialog, { onSubscribe: handleSubscribeConfirm, onCancel: () => setSubscribeDialogOpen(false) }), document.body), mergeState &&
652
- core.createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
737
+ core.createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, sourceColor: sourceCalendarColor, targetName: targetCalendarName, targetColor: targetCalendarColor, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
653
738
  core.createPortal(u(DeleteCalendarDialog, { calendarId: deleteState.calendarId, calendarName: deleteCalendarName, calendars: calendars, step: deleteState.step, onStepChange: step => setDeleteState(prev => (prev ? Object.assign(Object.assign({}, prev), { step }) : null)), onConfirmDelete: handleConfirmDelete, onCancel: () => setDeleteState(null), onMergeSelect: handleDeleteMergeSelect }), document.body), customColorPicker &&
654
739
  core.createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
655
740
  app.updateCalendar(customColorPicker.calendarId, {});
@@ -0,0 +1,6 @@
1
+ export interface CalendarChipProps {
2
+ name: string;
3
+ /** lineColor hex, e.g. "#3b82f6" */
4
+ color: string;
5
+ }
6
+ export declare const CalendarChip: ({ name, color }: CalendarChipProps) => import("preact").JSX.Element;
@@ -1,8 +1,10 @@
1
1
  interface MergeCalendarDialogProps {
2
2
  sourceName: string;
3
+ sourceColor: string;
3
4
  targetName: string;
5
+ targetColor: string;
4
6
  onConfirm: () => void;
5
7
  onCancel: () => void;
6
8
  }
7
- export declare const MergeCalendarDialog: ({ sourceName, targetName, onConfirm, onCancel, }: MergeCalendarDialogProps) => import("preact").JSX.Element;
9
+ export declare const MergeCalendarDialog: ({ sourceName, sourceColor, targetName, targetColor, onConfirm, onCancel, }: MergeCalendarDialogProps) => import("preact").JSX.Element;
8
10
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayflow/plugin-sidebar",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Sidebar plugin for DayFlow calendar",
5
5
  "keywords": [
6
6
  "calendar",
@@ -37,10 +37,10 @@
37
37
  "rollup-plugin-dts": "^6.3.0",
38
38
  "temporal-polyfill": "^0.3.0",
39
39
  "typescript": "^5.9.3",
40
- "@dayflow/core": "3.3.1"
40
+ "@dayflow/core": "3.3.2"
41
41
  },
42
42
  "peerDependencies": {
43
- "@dayflow/core": "3.3.1"
43
+ "@dayflow/core": "3.3.2"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && rollup -c",