@bimetal/calendar-svelte-components 0.30.0 → 0.32.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/dist/components/Calendar.svelte +63 -30
- package/dist/components/Calendar.svelte.d.ts +14 -2
- package/dist/components/Calendar.svelte.d.ts.map +1 -1
- package/dist/components/CalendarHeader.svelte +10 -10
- package/dist/components/CategoryFilter.svelte +2 -2
- package/dist/components/EventBar.svelte +10 -12
- package/dist/components/EventDialog.svelte +50 -50
- package/dist/components/EventHistory.svelte +17 -21
- package/dist/components/EventHistory.svelte.d.ts.map +1 -1
- package/dist/components/ResourceFilter.svelte +2 -2
- package/dist/components/UndoToast.svelte +5 -5
- package/dist/views/agenda/AgendaEvent.svelte +6 -6
- package/dist/views/agenda/AgendaView.svelte +9 -9
- package/dist/views/day/AllDayEvent.svelte +1 -1
- package/dist/views/day/DayView.svelte +16 -16
- package/dist/views/month/MonthView.svelte +4 -4
- package/dist/views/month/TimedEntry.svelte +4 -4
- package/dist/views/month/WeekRow.svelte +13 -13
- package/dist/views/resource/ResourceView.svelte +18 -18
- package/dist/views/timeline/TimelineView.svelte +13 -13
- package/dist/views/week/AllDayRow.svelte +3 -3
- package/dist/views/week/TimeEvent.svelte +5 -7
- package/dist/views/week/WeekView.svelte +15 -15
- package/dist/views/year/YearView.svelte +12 -12
- package/package.json +9 -8
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { CalendarStore } from '@bimetal/calendar-data';
|
|
5
5
|
import type { CalendarRule } from '@bimetal/calendar-rules';
|
|
6
6
|
import { parseDragInfo } from '@bimetal/calendar-headless';
|
|
7
|
+
import { isEditableTarget } from '@bimetal/a11y-dom';
|
|
7
8
|
import {
|
|
8
9
|
setCalendarContext,
|
|
9
10
|
setCalendarConfig,
|
|
@@ -11,8 +12,9 @@
|
|
|
11
12
|
} from '../binding/index.js';
|
|
12
13
|
import type {
|
|
13
14
|
CalendarConfig, CalendarConfigInput, CustomViewDefinition, EventDialogMode, RenderEventFn, AnchorRect,
|
|
15
|
+
CalendarController, CalendarControllerSnapshot,
|
|
14
16
|
} from '../binding/index.js';
|
|
15
|
-
import { onDestroy, untrack } from 'svelte';
|
|
17
|
+
import { onMount, onDestroy, untrack } from 'svelte';
|
|
16
18
|
import CalendarHeader from './CalendarHeader.svelte';
|
|
17
19
|
import EventDialog from './EventDialog.svelte';
|
|
18
20
|
import UndoToast from './UndoToast.svelte';
|
|
@@ -27,29 +29,41 @@
|
|
|
27
29
|
import TimelineView from '../views/timeline/TimelineView.svelte';
|
|
28
30
|
|
|
29
31
|
let {
|
|
30
|
-
|
|
32
|
+
controller = undefined,
|
|
33
|
+
store = undefined,
|
|
31
34
|
rules = undefined,
|
|
32
35
|
config: configInput = undefined,
|
|
33
36
|
darkMode = false,
|
|
37
|
+
dir = undefined,
|
|
34
38
|
showCategoryFilter = false,
|
|
35
39
|
showResourceFilter = false,
|
|
36
40
|
customViews = undefined,
|
|
37
41
|
eventDialog = { kind: 'builtin' },
|
|
38
42
|
onEventClick: consumerOnEventClick = undefined,
|
|
39
43
|
onNewEventRequested: consumerOnNewEventRequested = undefined,
|
|
44
|
+
onSelectionChange = undefined,
|
|
40
45
|
renderEvent = undefined,
|
|
41
46
|
onError = undefined,
|
|
42
47
|
}: {
|
|
43
|
-
|
|
48
|
+
/** BOUND (composition contract, F1): an already-owned controller (host-owned).
|
|
49
|
+
* The component only subscribes + renders — it never creates, destroys, or
|
|
50
|
+
* reconfigures it (the host owns lifecycle, dialog mode, and delegation). */
|
|
51
|
+
controller?: CalendarController;
|
|
52
|
+
/** STANDALONE: the component creates + owns a controller from these. */
|
|
53
|
+
store?: CalendarStore;
|
|
44
54
|
rules?: CalendarRule[];
|
|
45
55
|
config?: CalendarConfigInput;
|
|
46
56
|
darkMode?: boolean;
|
|
57
|
+
dir?: 'ltr' | 'rtl';
|
|
47
58
|
showCategoryFilter?: boolean;
|
|
48
59
|
showResourceFilter?: boolean;
|
|
49
60
|
customViews?: readonly CustomViewDefinition[];
|
|
50
61
|
eventDialog?: EventDialogMode;
|
|
51
62
|
onEventClick?: (eventId: string, anchorRect: AnchorRect, instanceDate?: number) => void;
|
|
52
63
|
onNewEventRequested?: (defaultDate: CalendarDateTime, anchorRect: AnchorRect, defaultResourceId?: string) => void;
|
|
64
|
+
/** OBSERVE hook: fires when the SELECTED EVENT changes (state, including clears)
|
|
65
|
+
* — a SEPARATE concern from the `none`-mode delegation callbacks. */
|
|
66
|
+
onSelectionChange?: (selectedEvent: { eventId: string; instanceDate?: number } | null) => void;
|
|
53
67
|
renderEvent?: RenderEventFn;
|
|
54
68
|
/** Controller error channel: domain rejections surface as toasts; any other
|
|
55
69
|
* error (e.g. ConcurrencyError) is passed here instead of rejecting. */
|
|
@@ -59,14 +73,19 @@
|
|
|
59
73
|
// ── Canonical controller binding ──────────────────────────────
|
|
60
74
|
// The controller owns ALL interaction state and semantics. This component is
|
|
61
75
|
// a thin bridge: it subscribes to the snapshot, forwards intents, and parses
|
|
62
|
-
// DOM events into DragInfo.
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
// DOM events into DragInfo.
|
|
77
|
+
//
|
|
78
|
+
// Dual-API (composition contract, F1): a passed `controller` selects the BOUND
|
|
79
|
+
// path (binding = null → subscribe only, never create/destroy/reconfigure; the
|
|
80
|
+
// host owns lifecycle + dialog mode). Otherwise STANDALONE creates + owns one.
|
|
81
|
+
// The controller is created ONCE (under `untrack`): `store` is immutable after
|
|
82
|
+
// mount — runes + `untrack` deliberately do NOT recreate it when the `store`
|
|
83
|
+
// prop changes; remount the component (e.g. via `{#key store}`) to switch
|
|
84
|
+
// stores. config/rules/customViews/eventDialog are captured here only as the
|
|
85
|
+
// initial value and then pushed reactively via the STANDALONE `$effect`s below,
|
|
86
|
+
// so the initial-value reads are deliberate.
|
|
87
|
+
const binding = untrack(() => (controller ? null : createCalendarControllerBinding({
|
|
88
|
+
store: store!,
|
|
70
89
|
config: configInput,
|
|
71
90
|
rules,
|
|
72
91
|
customViews,
|
|
@@ -75,28 +94,42 @@
|
|
|
75
94
|
// never go stale even though the controller is created once.
|
|
76
95
|
onEventClickExternal: (id, rect, inst) => consumerOnEventClick?.(id, rect, inst),
|
|
77
96
|
onNewEventRequestedExternal: (date, rect, rid) => consumerOnNewEventRequested?.(date, rect, rid),
|
|
97
|
+
// OBSERVE: fires on selection state change (separate from `none` delegation).
|
|
98
|
+
onSelectionChange: (sel) => onSelectionChange?.(sel),
|
|
78
99
|
onError: (err) => onError?.(err),
|
|
79
|
-
}));
|
|
80
|
-
|
|
100
|
+
})));
|
|
101
|
+
// `bound` is a mount-time decision (like the immutable `store`): captured once.
|
|
102
|
+
const bound = untrack(() => controller !== undefined);
|
|
103
|
+
const ctrl: CalendarController = controller ?? binding!.ctrl;
|
|
81
104
|
|
|
82
|
-
// `$
|
|
83
|
-
//
|
|
84
|
-
|
|
105
|
+
// Snapshot is a `$state` refreshed on subscribe — one path for BOUND and
|
|
106
|
+
// STANDALONE alike (both `ctrl.subscribe`/`getSnapshot` off the same contract).
|
|
107
|
+
let snapshot = $state<CalendarControllerSnapshot>(untrack(() => ctrl.getSnapshot()));
|
|
108
|
+
onMount(() => {
|
|
109
|
+
snapshot = ctrl.getSnapshot();
|
|
110
|
+
return ctrl.subscribe(() => (snapshot = ctrl.getSnapshot()));
|
|
111
|
+
});
|
|
85
112
|
|
|
86
|
-
// ── Reactive option updates
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
// ── Reactive option updates — STANDALONE only. The bound host owns lifecycle,
|
|
114
|
+
// config, dialog mode and delegation, so the bound path reconfigures NOTHING. ──
|
|
115
|
+
if (!bound) {
|
|
116
|
+
$effect(() => { ctrl.setConfig(configInput); });
|
|
117
|
+
$effect(() => { ctrl.setRules(rules); });
|
|
118
|
+
$effect(() => { ctrl.setCustomViews(customViews); });
|
|
119
|
+
// F8: dialog mode is a dynamic prop — keep the (owned) controller in sync.
|
|
120
|
+
$effect(() => { ctrl.setEventDialogKind(eventDialog.kind); });
|
|
92
121
|
|
|
93
|
-
|
|
122
|
+
onDestroy(() => binding!.destroy());
|
|
123
|
+
}
|
|
94
124
|
|
|
95
125
|
// ── v0.29 Tastatur: root-scoped keydown → controller (Ctrl/Cmd+Z undo, Escape
|
|
96
|
-
// clears selection). Bound on the `.
|
|
126
|
+
// clears selection). Bound on the `.bm-cal` root below (onkeydown) — NOT a window
|
|
97
127
|
// listener (no document-level routing). Semantics live in the controller. ──
|
|
98
128
|
function onKey(e: KeyboardEvent) {
|
|
99
|
-
ctrl.handleKeydown(
|
|
129
|
+
ctrl.handleKeydown({
|
|
130
|
+
key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey,
|
|
131
|
+
isEditable: isEditableTarget(e.target), preventDefault: () => e.preventDefault(),
|
|
132
|
+
});
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
// ── DOM-event → controller-intent bindings (the only place DOM is parsed) ──
|
|
@@ -154,8 +187,8 @@
|
|
|
154
187
|
onDragStart: ctrl.startDrag,
|
|
155
188
|
onHidePreview: () => ctrl.hidePreview(),
|
|
156
189
|
onDragEnd: () => ctrl.endDrag(),
|
|
157
|
-
get selectedEventId() { return snapshot.
|
|
158
|
-
get selectedInstanceDate() { return snapshot.
|
|
190
|
+
get selectedEventId() { return snapshot.selectedEvent?.eventId ?? null; },
|
|
191
|
+
get selectedInstanceDate() { return snapshot.selectedEvent?.instanceDate; },
|
|
159
192
|
onSelectEvent: (eventId: string, instanceDate?: number) => ctrl.selectEvent(eventId, instanceDate),
|
|
160
193
|
onClearSelection: () => ctrl.clearSelection(),
|
|
161
194
|
onUndo: (entityId: string) => ctrl.undo(entityId),
|
|
@@ -167,7 +200,7 @@
|
|
|
167
200
|
</script>
|
|
168
201
|
|
|
169
202
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
170
|
-
<div class="
|
|
203
|
+
<div class="bm-cal" data-theme={darkMode ? 'dark' : undefined} dir={dir} onkeydown={onKey}>
|
|
171
204
|
<CalendarHeader
|
|
172
205
|
title={snapshot.title}
|
|
173
206
|
currentView={snapshot.currentView}
|
|
@@ -228,8 +261,8 @@
|
|
|
228
261
|
config={snapshot.config}
|
|
229
262
|
onNewEvent={(d, anchorRect) => ctrl.openCreate(d, anchorRect ?? new DOMRect(0, 0, 0, 0))}
|
|
230
263
|
onEventClick={(eventId, anchorRect, instanceDate) => ctrl.openEdit(eventId, anchorRect, instanceDate)}
|
|
231
|
-
selectedEventId={snapshot.
|
|
232
|
-
selectedInstanceDate={snapshot.
|
|
264
|
+
selectedEventId={snapshot.selectedEvent?.eventId ?? null}
|
|
265
|
+
selectedInstanceDate={snapshot.selectedEvent?.instanceDate}
|
|
233
266
|
onSelectEvent={(eventId, instanceDate) => ctrl.selectEvent(eventId, instanceDate)}
|
|
234
267
|
onClearSelection={() => ctrl.clearSelection()}
|
|
235
268
|
/>
|
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import type { CalendarDateTime } from '@bimetal/core';
|
|
2
2
|
import type { CalendarStore } from '@bimetal/calendar-data';
|
|
3
3
|
import type { CalendarRule } from '@bimetal/calendar-rules';
|
|
4
|
-
import type { CalendarConfigInput, CustomViewDefinition, EventDialogMode, RenderEventFn, AnchorRect } from '../binding/index.js';
|
|
4
|
+
import type { CalendarConfigInput, CustomViewDefinition, EventDialogMode, RenderEventFn, AnchorRect, CalendarController } from '../binding/index.js';
|
|
5
5
|
type $$ComponentProps = {
|
|
6
|
-
|
|
6
|
+
/** BOUND (composition contract, F1): an already-owned controller (host-owned).
|
|
7
|
+
* The component only subscribes + renders — it never creates, destroys, or
|
|
8
|
+
* reconfigures it (the host owns lifecycle, dialog mode, and delegation). */
|
|
9
|
+
controller?: CalendarController;
|
|
10
|
+
/** STANDALONE: the component creates + owns a controller from these. */
|
|
11
|
+
store?: CalendarStore;
|
|
7
12
|
rules?: CalendarRule[];
|
|
8
13
|
config?: CalendarConfigInput;
|
|
9
14
|
darkMode?: boolean;
|
|
15
|
+
dir?: 'ltr' | 'rtl';
|
|
10
16
|
showCategoryFilter?: boolean;
|
|
11
17
|
showResourceFilter?: boolean;
|
|
12
18
|
customViews?: readonly CustomViewDefinition[];
|
|
13
19
|
eventDialog?: EventDialogMode;
|
|
14
20
|
onEventClick?: (eventId: string, anchorRect: AnchorRect, instanceDate?: number) => void;
|
|
15
21
|
onNewEventRequested?: (defaultDate: CalendarDateTime, anchorRect: AnchorRect, defaultResourceId?: string) => void;
|
|
22
|
+
/** OBSERVE hook: fires when the SELECTED EVENT changes (state, including clears)
|
|
23
|
+
* — a SEPARATE concern from the `none`-mode delegation callbacks. */
|
|
24
|
+
onSelectionChange?: (selectedEvent: {
|
|
25
|
+
eventId: string;
|
|
26
|
+
instanceDate?: number;
|
|
27
|
+
} | null) => void;
|
|
16
28
|
renderEvent?: RenderEventFn;
|
|
17
29
|
/** Controller error channel: domain rejections surface as toasts; any other
|
|
18
30
|
* error (e.g. ConcurrencyError) is passed here instead of rejecting. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Calendar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Calendar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAQ5D,OAAO,KAAK,EACQ,mBAAmB,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EACrG,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAe9B,KAAK,gBAAgB,GAAI;IACtB;;kFAE8E;IAC9E,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,wEAAwE;IACxE,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC9C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxF,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClH;0EACsE;IACtE,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/F,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B;6EACyE;IACzE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC,CAAC;AAyNJ,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -24,24 +24,24 @@
|
|
|
24
24
|
const config = getCalendarConfig();
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
|
-
<div class="
|
|
28
|
-
<h1 class="
|
|
29
|
-
<div class="
|
|
30
|
-
<div class="
|
|
27
|
+
<div class="bm-cal__header">
|
|
28
|
+
<h1 class="bm-cal__month-title">{title}</h1>
|
|
29
|
+
<div class="bm-cal__header-controls">
|
|
30
|
+
<div class="bm-cal__view-switcher">
|
|
31
31
|
{#each viewConfigs as vc (vc.type)}
|
|
32
32
|
<button
|
|
33
|
-
class="
|
|
34
|
-
class:
|
|
33
|
+
class="bm-cal__view-btn"
|
|
34
|
+
class:bm-cal__view-btn--active={currentView === vc.type}
|
|
35
35
|
onclick={() => onViewChange(vc.type)}
|
|
36
36
|
>
|
|
37
37
|
{vc.label}
|
|
38
38
|
</button>
|
|
39
39
|
{/each}
|
|
40
40
|
</div>
|
|
41
|
-
<div class="
|
|
42
|
-
<button class="
|
|
43
|
-
<button class="
|
|
44
|
-
<button class="
|
|
41
|
+
<div class="bm-cal__nav-controls">
|
|
42
|
+
<button class="bm-cal__nav" aria-label={config.locale.prev} onclick={onPrev}>‹</button>
|
|
43
|
+
<button class="bm-cal__today-btn" onclick={onToday}>{config.locale.today}</button>
|
|
44
|
+
<button class="bm-cal__nav" aria-label={config.locale.next} onclick={onNext}>›</button>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
{#if categories.length > 0}
|
|
16
|
-
<div class="category-filter">
|
|
16
|
+
<div class="bm-cal-category-filter">
|
|
17
17
|
{#each categories as cat (cat.name)}
|
|
18
18
|
{@const isHidden = hiddenCategories.has(cat.name)}
|
|
19
19
|
<button
|
|
20
|
-
class="category-filter__pill{isHidden ? ' category-filter__pill--hidden' : ''}"
|
|
20
|
+
class="bm-cal-category-filter__pill{isHidden ? ' bm-cal-category-filter__pill--hidden' : ''}"
|
|
21
21
|
style:background-color={cat.color}
|
|
22
22
|
onclick={() => onToggle(cat.name)}
|
|
23
23
|
type="button"
|
|
@@ -88,10 +88,10 @@
|
|
|
88
88
|
const isBar = $derived(!isCompact);
|
|
89
89
|
const color = $derived(event.metadata.color ?? '#007AFF');
|
|
90
90
|
const isRecurring = $derived(isExpandedRecurrenceInstance(event) || !!event.recurrence);
|
|
91
|
-
const ghostClass = $derived(ghost ? '
|
|
91
|
+
const ghostClass = $derived(ghost ? ' bm-cal__event-bar--ghost' : '');
|
|
92
92
|
const instanceDate = $derived(getRecurrenceInstanceMetadata(event)?.instanceDate);
|
|
93
93
|
const isSelected = $derived(ctx.selectedEventId === event.id && ctx.selectedInstanceDate === instanceDate);
|
|
94
|
-
const selectedClass = $derived(isSelected ? '
|
|
94
|
+
const selectedClass = $derived(isSelected ? ' bm-cal__event-bar--selected' : '');
|
|
95
95
|
|
|
96
96
|
const gridStyle = $derived(`grid-column: ${startCol + 1} / span ${span}; grid-row: ${lane + 1};`);
|
|
97
97
|
const barStyle = $derived(
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
|
|
119
119
|
{#if isCompact}
|
|
120
120
|
<div
|
|
121
|
-
class="
|
|
121
|
+
class="bm-cal__event-bar bm-cal__event-bar--single{ghostClass}{selectedClass}{extraClass}"
|
|
122
122
|
style={fullStyle}
|
|
123
123
|
draggable={!ghost}
|
|
124
124
|
role="button"
|
|
@@ -134,9 +134,9 @@
|
|
|
134
134
|
{#if slots.content}
|
|
135
135
|
{#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
|
|
136
136
|
{:else}
|
|
137
|
-
<span class="
|
|
138
|
-
<span class="
|
|
139
|
-
<span class="
|
|
137
|
+
<span class="bm-cal__event-dot" style:background-color={color}></span>
|
|
138
|
+
<span class="bm-cal__event-time">{timeStr}</span>
|
|
139
|
+
<span class="bm-cal__event-title bm-cal__event-title--dark">{isRecurring ? '↻ ' : ''}{renderedTitle}</span>
|
|
140
140
|
{/if}
|
|
141
141
|
{#if slots.badges}
|
|
142
142
|
{#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
</div>
|
|
145
145
|
{:else}
|
|
146
146
|
<div
|
|
147
|
-
class="
|
|
147
|
+
class="bm-cal__event-bar bm-cal__event-bar--multi{ghostClass}{selectedClass}{extraClass}"
|
|
148
148
|
style={fullStyle}
|
|
149
149
|
draggable={!ghost}
|
|
150
150
|
role="button"
|
|
@@ -160,9 +160,8 @@
|
|
|
160
160
|
{#if isBar && isStart && !ghost}
|
|
161
161
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_no_noninteractive_tabindex a11y_no_static_element_interactions -->
|
|
162
162
|
<div
|
|
163
|
-
class="
|
|
163
|
+
class="bm-cal__resize-handle bm-cal__resize-handle--start"
|
|
164
164
|
role="separator"
|
|
165
|
-
aria-label="Resize start"
|
|
166
165
|
draggable={true}
|
|
167
166
|
ondragstart={(e) => handleResizeDragStart(e, 'start')}
|
|
168
167
|
ondragend={ctx.onDragEnd}
|
|
@@ -172,7 +171,7 @@
|
|
|
172
171
|
{#if slots.content}
|
|
173
172
|
{#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
|
|
174
173
|
{:else}
|
|
175
|
-
<span class="
|
|
174
|
+
<span class="bm-cal__event-title{eventBarWantsDarkText(color) ? ' bm-cal__event-title--on-light' : ''}">{isRecurring ? '↻ ' : ''}{renderedTitle}</span>
|
|
176
175
|
{/if}
|
|
177
176
|
{#if slots.badges}
|
|
178
177
|
{#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
|
|
@@ -180,9 +179,8 @@
|
|
|
180
179
|
{#if isBar && isEnd && !ghost}
|
|
181
180
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_no_noninteractive_tabindex a11y_no_static_element_interactions -->
|
|
182
181
|
<div
|
|
183
|
-
class="
|
|
182
|
+
class="bm-cal__resize-handle bm-cal__resize-handle--end"
|
|
184
183
|
role="separator"
|
|
185
|
-
aria-label="Resize end"
|
|
186
184
|
draggable={true}
|
|
187
185
|
ondragstart={(e) => handleResizeDragStart(e, 'end')}
|
|
188
186
|
ondragend={ctx.onDragEnd}
|
|
@@ -88,11 +88,11 @@
|
|
|
88
88
|
|
|
89
89
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
90
90
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
91
|
-
<div class="
|
|
91
|
+
<div class="bm-cal__dialog-backdrop" onclick={onClose}></div>
|
|
92
92
|
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
93
93
|
<div
|
|
94
94
|
bind:this={dialogEl}
|
|
95
|
-
class="
|
|
95
|
+
class="bm-cal__dialog"
|
|
96
96
|
role="dialog"
|
|
97
97
|
aria-modal="true"
|
|
98
98
|
aria-label={isNew ? config.locale.newEvent : config.locale.editEvent}
|
|
@@ -107,24 +107,24 @@
|
|
|
107
107
|
/>
|
|
108
108
|
{:else}
|
|
109
109
|
<!-- Header -->
|
|
110
|
-
<div class="
|
|
111
|
-
<span class="
|
|
110
|
+
<div class="bm-cal__dialog-header">
|
|
111
|
+
<span class="bm-cal__dialog-title">
|
|
112
112
|
{isNew ? config.locale.newEvent : config.locale.editEvent}
|
|
113
113
|
</span>
|
|
114
|
-
<button class="
|
|
114
|
+
<button class="bm-cal__dialog-close" aria-label={config.locale.close} onclick={onClose}>×</button>
|
|
115
115
|
</div>
|
|
116
116
|
|
|
117
117
|
<!-- Body -->
|
|
118
|
-
<div class="
|
|
118
|
+
<div class="bm-cal__dialog-body">
|
|
119
119
|
<input
|
|
120
|
-
class="
|
|
120
|
+
class="bm-cal__dialog-input bm-cal__dialog-input--title"
|
|
121
121
|
type="text"
|
|
122
122
|
placeholder={config.locale.titlePlaceholder}
|
|
123
123
|
value={draft.title}
|
|
124
124
|
oninput={(e) => setDraft({ title: (e.target as HTMLInputElement).value })}
|
|
125
125
|
/>
|
|
126
126
|
|
|
127
|
-
<label class="
|
|
127
|
+
<label class="bm-cal__dialog-toggle">
|
|
128
128
|
<span>{config.locale.allDay}</span>
|
|
129
129
|
<input
|
|
130
130
|
type="checkbox"
|
|
@@ -134,9 +134,9 @@
|
|
|
134
134
|
</label>
|
|
135
135
|
|
|
136
136
|
{#if !isInstance}
|
|
137
|
-
<div class="
|
|
138
|
-
<label class="
|
|
139
|
-
<span>
|
|
137
|
+
<div class="bm-cal__dialog-recurrence">
|
|
138
|
+
<label class="bm-cal__dialog-toggle">
|
|
139
|
+
<span>{config.locale.recurrence}</span>
|
|
140
140
|
<input
|
|
141
141
|
type="checkbox"
|
|
142
142
|
checked={draft.recurring}
|
|
@@ -144,22 +144,22 @@
|
|
|
144
144
|
/>
|
|
145
145
|
</label>
|
|
146
146
|
{#if draft.recurring}
|
|
147
|
-
<div class="
|
|
148
|
-
<span style="font-size: 13px;">
|
|
149
|
-
<input class="
|
|
147
|
+
<div class="bm-cal__dialog-row" style="gap: 8px; align-items: center;">
|
|
148
|
+
<span style="font-size: 13px;">{config.locale.recurEvery}</span>
|
|
149
|
+
<input class="bm-cal__dialog-input" type="number" min="1" max="99" style="width: 50px;"
|
|
150
150
|
value={draft.recInterval}
|
|
151
151
|
oninput={(e) => setDraft({ recInterval: parseInt((e.target as HTMLInputElement).value) })} />
|
|
152
|
-
<select class="
|
|
152
|
+
<select class="bm-cal__dialog-input"
|
|
153
153
|
value={draft.recFreq}
|
|
154
154
|
onchange={(e) => setDraft({ recFreq: (e.target as HTMLSelectElement).value as EventDraft['recFreq'] })}>
|
|
155
|
-
<option value="daily">
|
|
156
|
-
<option value="weekly">
|
|
157
|
-
<option value="monthly">
|
|
158
|
-
<option value="yearly">
|
|
155
|
+
<option value="daily">{config.locale.recurUnits.daily}</option>
|
|
156
|
+
<option value="weekly">{config.locale.recurUnits.weekly}</option>
|
|
157
|
+
<option value="monthly">{config.locale.recurUnits.monthly}</option>
|
|
158
|
+
<option value="yearly">{config.locale.recurUnits.yearly}</option>
|
|
159
159
|
</select>
|
|
160
|
-
<label class="
|
|
161
|
-
<span style="font-size: 13px;">
|
|
162
|
-
<input class="
|
|
160
|
+
<label class="bm-cal__dialog-label" style="flex-direction: row; align-items: center; gap: 6px;">
|
|
161
|
+
<span style="font-size: 13px;">{config.locale.recurUntil}</span>
|
|
162
|
+
<input class="bm-cal__dialog-input" type="date"
|
|
163
163
|
value={draft.recUntil}
|
|
164
164
|
onchange={(e) => setDraft({ recUntil: (e.target as HTMLInputElement).value })} />
|
|
165
165
|
</label>
|
|
@@ -170,16 +170,16 @@
|
|
|
170
170
|
|
|
171
171
|
{#if isInstance}
|
|
172
172
|
<div style="font-size: 12px; color: var(--cal-color-text-muted); padding: 4px 0;">
|
|
173
|
-
↻
|
|
173
|
+
↻ {config.locale.recurringInstanceNote}
|
|
174
174
|
</div>
|
|
175
175
|
{/if}
|
|
176
176
|
|
|
177
177
|
<!-- Start date row -->
|
|
178
|
-
<div class="
|
|
179
|
-
<label class="
|
|
178
|
+
<div class="bm-cal__dialog-row">
|
|
179
|
+
<label class="bm-cal__dialog-label">
|
|
180
180
|
<span>{config.locale.from}</span>
|
|
181
181
|
<input
|
|
182
|
-
class="
|
|
182
|
+
class="bm-cal__dialog-input"
|
|
183
183
|
type="date"
|
|
184
184
|
value={draft.startDate}
|
|
185
185
|
onchange={(e) => setDraft({ startDate: (e.target as HTMLInputElement).value })}
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
</label>
|
|
188
188
|
{#if !draft.allDay}
|
|
189
189
|
<input
|
|
190
|
-
class="
|
|
190
|
+
class="bm-cal__dialog-input bm-cal__dialog-input--time"
|
|
191
191
|
type="time"
|
|
192
192
|
value={draft.startTime}
|
|
193
193
|
onchange={(e) => setDraft({ startTime: (e.target as HTMLInputElement).value })}
|
|
@@ -196,11 +196,11 @@
|
|
|
196
196
|
</div>
|
|
197
197
|
|
|
198
198
|
<!-- End date row -->
|
|
199
|
-
<div class="
|
|
200
|
-
<label class="
|
|
199
|
+
<div class="bm-cal__dialog-row">
|
|
200
|
+
<label class="bm-cal__dialog-label">
|
|
201
201
|
<span>{config.locale.to}</span>
|
|
202
202
|
<input
|
|
203
|
-
class="
|
|
203
|
+
class="bm-cal__dialog-input"
|
|
204
204
|
type="date"
|
|
205
205
|
value={draft.endDate}
|
|
206
206
|
min={draft.startDate}
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
</label>
|
|
210
210
|
{#if !draft.allDay}
|
|
211
211
|
<input
|
|
212
|
-
class="
|
|
212
|
+
class="bm-cal__dialog-input bm-cal__dialog-input--time"
|
|
213
213
|
type="time"
|
|
214
214
|
value={draft.endTime}
|
|
215
215
|
onchange={(e) => setDraft({ endTime: (e.target as HTMLInputElement).value })}
|
|
@@ -218,11 +218,11 @@
|
|
|
218
218
|
</div>
|
|
219
219
|
|
|
220
220
|
<!-- Category -->
|
|
221
|
-
<label class="
|
|
221
|
+
<label class="bm-cal__dialog-label">
|
|
222
222
|
<span>{config.locale.category}</span>
|
|
223
|
-
<div class="
|
|
223
|
+
<div class="bm-cal__dialog-category-row">
|
|
224
224
|
<select
|
|
225
|
-
class="
|
|
225
|
+
class="bm-cal__dialog-input"
|
|
226
226
|
value={draft.category}
|
|
227
227
|
onchange={(e) => setDraft({ category: (e.target as HTMLSelectElement).value })}
|
|
228
228
|
>
|
|
@@ -230,15 +230,15 @@
|
|
|
230
230
|
<option value={cat.name}>{cat.name}</option>
|
|
231
231
|
{/each}
|
|
232
232
|
</select>
|
|
233
|
-
<span class="
|
|
233
|
+
<span class="bm-cal__category-dot" style:background-color={draft.color}></span>
|
|
234
234
|
</div>
|
|
235
235
|
</label>
|
|
236
236
|
|
|
237
237
|
{#if config.resources.length > 0}
|
|
238
|
-
<label class="
|
|
239
|
-
<span>{config.locale.resources
|
|
238
|
+
<label class="bm-cal__dialog-label">
|
|
239
|
+
<span>{config.locale.resources}</span>
|
|
240
240
|
<select
|
|
241
|
-
class="
|
|
241
|
+
class="bm-cal__dialog-input"
|
|
242
242
|
value={draft.resourceId}
|
|
243
243
|
onchange={(e) => setDraft({ resourceId: (e.target as HTMLSelectElement).value })}
|
|
244
244
|
>
|
|
@@ -251,10 +251,10 @@
|
|
|
251
251
|
{/if}
|
|
252
252
|
|
|
253
253
|
<!-- Location -->
|
|
254
|
-
<label class="
|
|
254
|
+
<label class="bm-cal__dialog-label">
|
|
255
255
|
<span>{config.locale.location}</span>
|
|
256
256
|
<input
|
|
257
|
-
class="
|
|
257
|
+
class="bm-cal__dialog-input"
|
|
258
258
|
type="text"
|
|
259
259
|
value={draft.location}
|
|
260
260
|
oninput={(e) => setDraft({ location: (e.target as HTMLInputElement).value })}
|
|
@@ -262,10 +262,10 @@
|
|
|
262
262
|
</label>
|
|
263
263
|
|
|
264
264
|
<!-- Description -->
|
|
265
|
-
<label class="
|
|
265
|
+
<label class="bm-cal__dialog-label">
|
|
266
266
|
<span>{config.locale.description}</span>
|
|
267
267
|
<textarea
|
|
268
|
-
class="
|
|
268
|
+
class="bm-cal__dialog-input bm-cal__dialog-input--description"
|
|
269
269
|
value={draft.description}
|
|
270
270
|
oninput={(e) => setDraft({ description: (e.target as HTMLTextAreaElement).value })}
|
|
271
271
|
></textarea>
|
|
@@ -274,33 +274,33 @@
|
|
|
274
274
|
|
|
275
275
|
<!-- Validation errors -->
|
|
276
276
|
{#if validationErrors.length > 0}
|
|
277
|
-
<div class="
|
|
277
|
+
<div class="bm-cal__dialog-errors">
|
|
278
278
|
{#each validationErrors as err, i (i)}
|
|
279
|
-
<div class="
|
|
279
|
+
<div class="bm-cal__dialog-error">{err}</div>
|
|
280
280
|
{/each}
|
|
281
281
|
</div>
|
|
282
282
|
{/if}
|
|
283
283
|
|
|
284
284
|
<!-- Footer -->
|
|
285
|
-
<div class="
|
|
285
|
+
<div class="bm-cal__dialog-footer">
|
|
286
286
|
{#if !isNew}
|
|
287
287
|
{#if config.locale.delete !== false}
|
|
288
|
-
<button class="
|
|
288
|
+
<button class="bm-cal__dialog-btn bm-cal__dialog-btn--delete" onclick={() => onDelete(event!.id)}>
|
|
289
289
|
{config.locale.delete}
|
|
290
290
|
</button>
|
|
291
291
|
{/if}
|
|
292
292
|
{#if history.length > 0}
|
|
293
|
-
<button class="
|
|
293
|
+
<button class="bm-cal__dialog-btn bm-cal__dialog-btn--history" onclick={() => { showHistory = true; }}>
|
|
294
294
|
{config.locale.history}
|
|
295
295
|
</button>
|
|
296
296
|
{/if}
|
|
297
297
|
{/if}
|
|
298
|
-
<div class="
|
|
299
|
-
<button class="
|
|
298
|
+
<div class="bm-cal__dialog-footer-right">
|
|
299
|
+
<button class="bm-cal__dialog-btn bm-cal__dialog-btn--cancel" onclick={onClose}>
|
|
300
300
|
{config.locale.cancel}
|
|
301
301
|
</button>
|
|
302
302
|
<button
|
|
303
|
-
class="
|
|
303
|
+
class="bm-cal__dialog-btn bm-cal__dialog-btn--save"
|
|
304
304
|
onclick={onSave}
|
|
305
305
|
disabled={!draft.title.trim()}
|
|
306
306
|
>
|