@ziix/calendar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,433 @@
1
+ import { Dayjs } from 'dayjs';
2
+
3
+ export declare function buildAxis(slot?: SlotConfig): SlotAxis;
4
+
5
+ /**
6
+ * The public, framework-agnostic calendar. Construct with a host element and
7
+ * options, then `render()`. Mirrors the imperative surface hosts rely on
8
+ * (render/destroy/refetchEvents/addEvent/getEventById/gotoDate/changeView/…) so
9
+ * Preact and React callers can drive it through a ref.
10
+ */
11
+ export declare class Calendar {
12
+ readonly el: HTMLElement;
13
+ options: CalendarOptions;
14
+ readonly events: EventStore;
15
+ readonly resources: ResourceStore;
16
+ private _date;
17
+ private _view;
18
+ private viewImpl;
19
+ private bodyEl;
20
+ private titleEl;
21
+ constructor(el: HTMLElement, options?: CalendarOptions);
22
+ get tz(): string | undefined;
23
+ get date(): Dayjs;
24
+ get view(): ViewType;
25
+ get axis(): SlotAxis;
26
+ get locale(): Locale;
27
+ get firstDay(): number;
28
+ /** Start of the currently-shown view range, in the calendar timezone. */
29
+ get activeStart(): Dayjs;
30
+ /** End of the currently-shown view range. */
31
+ get activeEnd(): Dayjs;
32
+ /** The active view's type and date window (parity with FullCalendar's `view`). */
33
+ getView(): {
34
+ type: ViewType;
35
+ activeStart: Dayjs;
36
+ activeEnd: Dayjs;
37
+ };
38
+ private isDayClosed;
39
+ get editable(): boolean;
40
+ get selectable(): boolean;
41
+ now(): Dayjs;
42
+ render(): this;
43
+ destroy(): void;
44
+ private createView;
45
+ private mountView;
46
+ changeView(type: ViewType): void;
47
+ gotoDate(date: string | Date | Dayjs): void;
48
+ today(): void;
49
+ prev(): void;
50
+ next(): void;
51
+ /** Refetch resources (if a function source), rebuild structure, then events. */
52
+ reload(): Promise<void>;
53
+ private refetchResources;
54
+ refetchEvents(): Promise<void>;
55
+ addEvent(input: EventInput): EventHandle;
56
+ getEventById(id: string | number): EventHandle | null;
57
+ getEvents(): CalEvent[];
58
+ getResources(): CalResource[];
59
+ getResourceById(id: string | number): ResourceHandle | null;
60
+ private resourceHandle;
61
+ private handle;
62
+ /** Build the inner body of an event, honouring the `renderEvent` hook. */
63
+ renderEventContent(event: CalEvent): HTMLElement;
64
+ private defaultEventContent;
65
+ fireEventClick(event: CalEvent, el: HTMLElement, jsEvent: MouseEvent): void;
66
+ fireEventMount(event: CalEvent, el: HTMLElement): void;
67
+ /** Attach a right-click handler to an event bar when `onEventContextMenu` is set. */
68
+ bindContextMenu(el: HTMLElement, event: CalEvent): void;
69
+ /** Whether events are allowed to overlap on the same resource (default true). */
70
+ private allowsOverlap;
71
+ private hasCollision;
72
+ /**
73
+ * Apply a drag/resize result: gate on `eventOverlap`, mutate the event,
74
+ * re-render, and fire `onEventChange`. Returns false (and reverts the live
75
+ * preview by re-rendering) when the move is rejected.
76
+ */
77
+ commitEventChange(event: CalEvent, start: Dayjs, end: Dayjs, resourceId: string | null): boolean;
78
+ /** Gate a drag-selection on `selectAllow`, then fire `onSelect`. */
79
+ commitSelect(start: Dayjs, end: Dayjs, resource: CalResource | null, jsEvent: MouseEvent): boolean;
80
+ private renderToolbar;
81
+ private renderToolbarToken;
82
+ private updateTitle;
83
+ private emitDatesSet;
84
+ }
85
+
86
+ export declare interface CalendarOptions {
87
+ view?: ViewType;
88
+ date?: string | Date;
89
+ locale?: Locale | string;
90
+ timezone?: string;
91
+ firstDay?: number;
92
+ weekends?: boolean;
93
+ slot?: SlotConfig;
94
+ /** Intl options for the default event time rendering. */
95
+ timeFormat?: Intl.DateTimeFormatOptions;
96
+ nowIndicator?: boolean;
97
+ /**
98
+ * Mark the shown day as closed/non-business — tints the time grid with the
99
+ * `--zc-nonbusiness` colour. Pass a boolean, or a predicate evaluated against
100
+ * the current date (re-evaluated on navigation).
101
+ */
102
+ dayClosed?: boolean | ((date: Dayjs) => boolean);
103
+ editable?: boolean;
104
+ selectable?: boolean;
105
+ height?: number | string;
106
+ /**
107
+ * Minimum height (px) of a single stacked event in the timeline. The resource
108
+ * row grows to `levels × eventMinHeight` so overlapping events keep this height
109
+ * instead of being squashed. Default 48.
110
+ */
111
+ eventMinHeight?: number;
112
+ toolbar?: ToolbarConfig | false;
113
+ buttons?: Record<string, CustomButton>;
114
+ resources?: ResourceSource;
115
+ resourceArea?: ResourceAreaConfig;
116
+ resourceGroupField?: string;
117
+ resourceOrder?: string;
118
+ events?: EventSource_2;
119
+ /** Custom event body renderer. Return a string (innerHTML) or a node. */
120
+ renderEvent?: (event: CalEvent) => HTMLElement | string;
121
+ /** Custom resource label renderer. */
122
+ renderResource?: (resource: CalResource) => HTMLElement | string;
123
+ eventOverlap?: boolean | (() => boolean);
124
+ selectAllow?: (info: {
125
+ start: Dayjs;
126
+ end: Dayjs;
127
+ resource: CalResource | null;
128
+ }) => boolean;
129
+ onEventClick?: (info: EventClickInfo) => void;
130
+ /** Right-click on an event. The native menu is suppressed before this fires. */
131
+ onEventContextMenu?: (info: EventContextMenuInfo) => void;
132
+ onEventChange?: (info: EventChangeInfo) => void;
133
+ onSelect?: (info: SelectInfo) => void;
134
+ onDatesSet?: (info: DatesSetInfo) => void;
135
+ onEventMount?: (info: EventMountInfo) => void;
136
+ onEventsSet?: (events: CalEvent[]) => void;
137
+ }
138
+
139
+ /** Normalised event used internally and exposed to callbacks. */
140
+ export declare interface CalEvent {
141
+ id: string;
142
+ title: string;
143
+ start: Dayjs;
144
+ end: Dayjs;
145
+ resourceId: string | null;
146
+ allDay: boolean;
147
+ color?: string;
148
+ textColor?: string;
149
+ extendedProps: Record<string, unknown>;
150
+ /** The original, untouched input. */
151
+ raw: EventInput;
152
+ }
153
+
154
+ /** Normalised resource used internally and exposed to callbacks. */
155
+ export declare interface CalResource {
156
+ id: string;
157
+ title: string;
158
+ group: string | null;
159
+ order: number;
160
+ /** Arbitrary domain data, mutable via the resource handle's setExtendedProp. */
161
+ extendedProps: Record<string, unknown>;
162
+ raw: ResourceInput;
163
+ }
164
+
165
+ export declare interface CustomButton {
166
+ text?: string;
167
+ /** CSS class for an icon span (e.g. an icon-font class). */
168
+ icon?: string;
169
+ onClick: (jsEvent: MouseEvent) => void;
170
+ }
171
+
172
+ /** A start/end window expressed in the calendar's timezone. */
173
+ export declare interface DateRange {
174
+ start: Dayjs;
175
+ end: Dayjs;
176
+ }
177
+
178
+ export declare interface DatesSetInfo {
179
+ start: Dayjs;
180
+ end: Dayjs;
181
+ view: ViewType;
182
+ }
183
+
184
+ /** Minutes from midnight for a Dayjs, read in its own (tz-applied) clock. */
185
+ export declare function dayMinutes(d: Dayjs): number;
186
+
187
+ export declare interface EventChangeInfo {
188
+ event: CalEvent;
189
+ oldEvent: CalEvent;
190
+ /** Undo the change (e.g. when a server persist fails) and re-render. */
191
+ revert: () => void;
192
+ }
193
+
194
+ export declare interface EventClickInfo {
195
+ event: CalEvent;
196
+ el: HTMLElement;
197
+ jsEvent: MouseEvent;
198
+ }
199
+
200
+ export declare interface EventContextMenuInfo {
201
+ event: CalEvent;
202
+ el: HTMLElement;
203
+ /** The contextmenu event; `preventDefault()` has already been called. */
204
+ jsEvent: MouseEvent;
205
+ }
206
+
207
+ /** Handle returned by addEvent/getEventById, mirroring the imperative API hosts rely on. */
208
+ export declare interface EventHandle {
209
+ id: string;
210
+ event: CalEvent;
211
+ remove(): void;
212
+ setExtendedProp(key: string, value: unknown): void;
213
+ }
214
+
215
+ /** Raw event as supplied by the host application. */
216
+ export declare interface EventInput {
217
+ id: string | number;
218
+ title?: string;
219
+ start: string | Date;
220
+ end?: string | Date;
221
+ resourceId?: string | number;
222
+ allDay?: boolean;
223
+ /** Background colour (any CSS colour). */
224
+ color?: string;
225
+ /** Foreground/text colour. */
226
+ textColor?: string;
227
+ /** Arbitrary domain data forwarded to renderEvent and callbacks. */
228
+ extendedProps?: Record<string, unknown>;
229
+ [key: string]: unknown;
230
+ }
231
+
232
+ export declare interface EventMountInfo {
233
+ event: CalEvent;
234
+ el: HTMLElement;
235
+ }
236
+
237
+ declare type EventSource_2 = EventInput[] | ((range: DateRange) => EventInput[] | Promise<EventInput[]>);
238
+ export { EventSource_2 as EventSource }
239
+
240
+ /** Normalises raw event input, dedupes by id, and answers range/resource queries. */
241
+ export declare class EventStore {
242
+ private tz?;
243
+ private map;
244
+ constructor(tz?: string | undefined);
245
+ normalize(input: EventInput): CalEvent;
246
+ /** Replace the entire set, deduping by id (last write wins). */
247
+ set(inputs: EventInput[]): void;
248
+ add(input: EventInput): CalEvent;
249
+ remove(id: string | number): boolean;
250
+ get(id: string | number): CalEvent | undefined;
251
+ all(): CalEvent[];
252
+ /**
253
+ * Events overlapping [start, end). When `resourceId` is supplied (including
254
+ * `null` for unassigned), only events on that resource are returned.
255
+ */
256
+ inRange(start: Dayjs, end: Dayjs, resourceId?: string | null): CalEvent[];
257
+ }
258
+
259
+ /** Format an absolute instant in the given timezone via Intl. */
260
+ export declare function intlFormat(d: Dayjs, opts: Intl.DateTimeFormatOptions, localeTag: string, tz?: string): string;
261
+
262
+ /**
263
+ * Locale strings and behaviour. This is the single place every piece of text the
264
+ * calendar renders itself can be translated — pass your app's translations here.
265
+ * All other visible text (column headers, resource labels, event content) is
266
+ * supplied by you through the render hooks, and dates are formatted via `Intl`
267
+ * using `intl`.
268
+ */
269
+ export declare interface Locale {
270
+ /** BCP-47-ish code, e.g. 'da'. */
271
+ code: string;
272
+ /** Day the week starts on (0 = Sunday). Overrides `firstDay` option when set. */
273
+ firstDay?: number;
274
+ /** Toolbar button labels. */
275
+ buttons?: {
276
+ today?: string;
277
+ prev?: string;
278
+ next?: string;
279
+ };
280
+ /** Accessible labels for icon buttons. */
281
+ ariaLabels?: {
282
+ today?: string;
283
+ prev?: string;
284
+ next?: string;
285
+ };
286
+ /** Intl locale tag for date formatting; defaults to `code`. */
287
+ intl?: string;
288
+ }
289
+
290
+ /** Format minutes-from-midnight back to a zero-padded 'HH:mm'. */
291
+ export declare function minutesToTime(min: number): string;
292
+
293
+ /** "Now" in the calendar timezone. */
294
+ export declare function nowTz(tz?: string): Dayjs;
295
+
296
+ export declare interface PackedEvent {
297
+ event: CalEvent;
298
+ /** Column index within the cluster. */
299
+ col: number;
300
+ /** Number of columns in the cluster. */
301
+ cols: number;
302
+ /** Left offset as a 0..1 fraction of the column width. */
303
+ left: number;
304
+ /** Width as a 0..1 fraction of the column width. */
305
+ width: number;
306
+ }
307
+
308
+ /**
309
+ * Pack events that share a single column (resource/day) into side-by-side
310
+ * sub-columns so overlapping events never cover each other — the classic
311
+ * interval-graph greedy colouring FullCalendar uses.
312
+ *
313
+ * Events are expected to already belong to the same column; callers filter by
314
+ * resource first.
315
+ */
316
+ export declare function packEvents(events: CalEvent[]): PackedEvent[];
317
+
318
+ export declare interface ResourceAreaConfig {
319
+ width?: number | string;
320
+ columns?: ResourceColumn[];
321
+ }
322
+
323
+ export declare interface ResourceColumn {
324
+ /** Resource field to read for a plain-text cell. */
325
+ field?: string;
326
+ /** Column header text. */
327
+ header?: string;
328
+ width?: number | string;
329
+ /** Custom cell renderer; takes precedence over `field`. */
330
+ render?: (resource: CalResource) => HTMLElement | string;
331
+ }
332
+
333
+ /** Handle returned by getResourceById; lets hosts push live data into a resource. */
334
+ export declare interface ResourceHandle {
335
+ id: string;
336
+ resource: CalResource;
337
+ /** Update an extendedProps value and re-render the resource area. */
338
+ setExtendedProp(key: string, value: unknown): void;
339
+ /** Update a top-level resource field (e.g. title) and re-render. */
340
+ setProp(key: 'title' | 'group', value: string): void;
341
+ }
342
+
343
+ /** Raw resource as supplied by the host application. */
344
+ export declare interface ResourceInput {
345
+ id: string | number;
346
+ title?: string;
347
+ group?: string;
348
+ order?: number;
349
+ /** Arbitrary domain data (e.g. workHours, punchinout, badges). */
350
+ extendedProps?: Record<string, unknown>;
351
+ [key: string]: unknown;
352
+ }
353
+
354
+ export declare type ResourceSource = ResourceInput[] | ((range: DateRange) => ResourceInput[] | Promise<ResourceInput[]>);
355
+
356
+ /** Normalises resources, reads the configured group/order fields, and sorts. */
357
+ export declare class ResourceStore {
358
+ private groupField;
359
+ private orderField;
360
+ private list;
361
+ constructor(groupField?: string, orderField?: string);
362
+ normalize(input: ResourceInput): CalResource;
363
+ set(inputs: ResourceInput[]): void;
364
+ all(): CalResource[];
365
+ get(id: string | number): CalResource | undefined;
366
+ /**
367
+ * Resources in display order. With `resourceOrder: 'id'` they are sorted by a
368
+ * natural id comparison (so 'E2' precedes 'E10'); with a numeric order field
369
+ * they sort by it; otherwise the original input order is preserved (sort is
370
+ * stable), matching FullCalendar's default.
371
+ */
372
+ ordered(): CalResource[];
373
+ /**
374
+ * Grouped resources preserving first-seen group order. Returns a flat list of
375
+ * `{ group, resources }` buckets; ungrouped resources land in a `null` bucket.
376
+ */
377
+ grouped(): Array<{
378
+ group: string | null;
379
+ resources: CalResource[];
380
+ }>;
381
+ }
382
+
383
+ export declare interface SelectInfo {
384
+ start: Dayjs;
385
+ end: Dayjs;
386
+ resource: CalResource | null;
387
+ jsEvent: MouseEvent | PointerEvent;
388
+ }
389
+
390
+ /** Resolved, numeric time-axis derived from a SlotConfig. */
391
+ export declare interface SlotAxis {
392
+ /** First visible minute from midnight. */
393
+ min: number;
394
+ /** Last visible minute from midnight. */
395
+ max: number;
396
+ /** Minutes per slot. */
397
+ duration: number;
398
+ /** Minutes between axis labels. */
399
+ labelInterval: number;
400
+ /** Number of whole slots between min and max. */
401
+ slots: number;
402
+ /** Total visible minutes (max - min). */
403
+ totalMinutes: number;
404
+ }
405
+
406
+ /** Vertical/horizontal time-axis configuration. */
407
+ export declare interface SlotConfig {
408
+ /** Slot granularity in minutes (default 15). */
409
+ duration?: number;
410
+ /** First visible time as 'HH:mm' (default '00:00'). */
411
+ min?: string;
412
+ /** Last visible time as 'HH:mm', '24:00' allowed (default '24:00'). */
413
+ max?: string;
414
+ /** Minutes between axis labels (default 60). */
415
+ labelInterval?: number;
416
+ }
417
+
418
+ /** Parse 'HH:mm' / 'HH:mm:ss' / '24:00' into minutes from midnight. */
419
+ export declare function timeToMinutes(t: string): number;
420
+
421
+ export declare interface ToolbarConfig {
422
+ start?: string;
423
+ center?: string;
424
+ end?: string;
425
+ }
426
+
427
+ /** Parse a value into a Dayjs anchored to `tz` (the shop timezone), if given. */
428
+ export declare function toTz(value: string | Date | Dayjs, tz?: string): Dayjs;
429
+
430
+ /** Built-in view identifiers. */
431
+ export declare type ViewType = 'day' | 'resource-day' | 'timeline';
432
+
433
+ export { }
@@ -0,0 +1 @@
1
+ .zc{--zc-border: #e2e2e2;--zc-bg: #ffffff;--zc-muted-bg: #f6f6f6;--zc-fg: #1a1a1a;--zc-muted-fg: #6b7280;--zc-today-bg: #f0f6ff;--zc-now: #ef4444;--zc-btn-bg: transparent;--zc-btn-fg: var(--zc-fg);--zc-btn-border: var(--zc-border);--zc-btn-hover-bg: var(--zc-muted-bg);--zc-btn-active-bg: #e7efff;--zc-event-bg: #c7dbff;--zc-event-border: #a9c6ff;--zc-event-fg: #1e3a5f;--zc-nonbusiness: oklch(0 0 0 / .045);--zc-radius: 6px;--zc-font: inherit;display:flex;flex-direction:column;box-sizing:border-box;font-family:var(--zc-font);color:var(--zc-fg);background:var(--zc-bg)}.zc *,.zc *:before,.zc *:after{box-sizing:border-box}.zc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;flex:0 0 auto}.zc-toolbar-section{display:flex;align-items:center;gap:.375rem}.zc-btn-group{display:inline-flex}.zc-btn-group .zc-btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0;margin-left:-1px}.zc-btn-group .zc-btn:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.zc-toolbar-center{flex:1 1 auto;justify-content:center}.zc-title{margin:0;font-size:1rem;font-weight:600;text-transform:capitalize}.zc-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;font:inherit;font-size:.875rem;line-height:1.2;padding:.375rem .75rem;border-radius:var(--zc-radius);border:1px solid var(--zc-btn-border);background:var(--zc-btn-bg);color:var(--zc-btn-fg)}.zc-btn:hover{background:var(--zc-btn-hover-bg)}.zc-btn:active{background:var(--zc-btn-active-bg)}.zc-body{flex:1 1 auto;overflow:auto;position:relative;border-top:1px solid var(--zc-border)}.zc-timegrid{display:flex;align-items:stretch;position:relative;min-height:100%}.zc-axis{position:relative;flex:0 0 56px;border-right:1px solid var(--zc-border)}.zc-axis-label{position:absolute;right:6px;transform:translateY(-50%);font-size:.6875rem;color:var(--zc-muted-fg);white-space:nowrap}.zc-col{position:relative;flex:1 1 auto}.zc-slot-line{position:absolute;left:0;right:0;border-top:1px solid var(--zc-border);opacity:.4}.zc-slot-line.zc-slot-major{opacity:1}.zc-event{position:absolute;overflow:hidden;border-radius:var(--zc-radius);border:1px solid var(--zc-event-border);background:var(--zc-event-bg);color:var(--zc-event-fg);font-size:.75rem;cursor:pointer}.zc-event-main{padding:2px 4px;height:100%}.zc-event-default{display:flex;flex-direction:column;gap:1px;line-height:1.2}.zc-event-time{font-variant-numeric:tabular-nums;opacity:.85}.zc-event-title{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.zc-timeline{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:stretch}.zc-tl-resource-area{display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--zc-border);background:var(--zc-bg)}.zc-tl-resource-head,.zc-tl-resource-row{display:flex;align-items:stretch;flex:0 0 auto}.zc-tl-resource-head{border-bottom:1px solid var(--zc-border);background:var(--zc-muted-bg);font-weight:600;font-size:.75rem}.zc-tl-col-head{display:flex;align-items:center;padding:0 .5rem;overflow:hidden;white-space:nowrap}.zc-tl-resource-body{flex:1 1 auto;overflow:hidden}.zc-tl-resource-row{border-bottom:1px solid var(--zc-border)}.zc-tl-col-cell{display:flex;flex-direction:column;justify-content:center;padding:.25rem .5rem;overflow:hidden;font-size:.8125rem;min-width:0}.zc-tl-group-row{display:flex;align-items:center;padding:0 .5rem;background:var(--zc-muted-bg);color:var(--zc-muted-fg);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.zc-tl-time-area{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;overflow:hidden}.zc-tl-time-head{flex:0 0 auto;overflow:hidden;position:relative;border-bottom:1px solid var(--zc-border);background:var(--zc-muted-bg)}.zc-tl-axis{position:relative;height:100%}.zc-tl-axis-label{position:absolute;top:0;bottom:0;display:flex;align-items:center;padding-left:4px;font-size:.6875rem;color:var(--zc-muted-fg);white-space:nowrap;border-left:1px solid var(--zc-border)}.zc-tl-time-body{flex:1 1 auto;overflow:auto;position:relative}.zc-tl-time-canvas{position:relative;min-height:100%}.zc-tl-overlay{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;z-index:0}.zc-tl-vline{position:absolute;top:0;bottom:0;border-left:1px solid var(--zc-border);opacity:.6}.zc-tl-now{position:absolute;top:0;bottom:0;border-left:2px solid var(--zc-now);z-index:3}.zc-tl-rows{position:relative;z-index:1}.zc-tl-row{position:relative;border-bottom:1px solid var(--zc-border)}.zc-tl-group-spacer{background:var(--zc-muted-bg);opacity:.5}.zc-tl-event{display:flex;align-items:stretch}.zc-tl-event .zc-event-main{padding:3px 6px}.zc-rg{position:absolute;top:0;right:0;bottom:0;left:0;overflow-y:auto;overflow-x:hidden}.zc-rg-head{position:sticky;top:0;z-index:5;display:flex;align-items:stretch;background:var(--zc-muted-bg);border-bottom:1px solid var(--zc-border)}.zc-rg-corner{flex:0 0 56px;width:56px;border-right:1px solid var(--zc-border)}.zc-rg-head-cols{flex:1 1 auto;display:flex;flex-direction:column;min-width:0}.zc-rg-group-row,.zc-rg-label-row{display:flex;align-items:stretch}.zc-rg-group-band{display:flex;align-items:center;justify-content:center;padding:2px 4px;border-left:1px solid var(--zc-border);font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--zc-muted-fg);overflow:hidden;white-space:nowrap}.zc-rg-label{flex:1 1 0;display:flex;align-items:center;justify-content:center;padding:4px 6px;border-left:1px solid var(--zc-border);font-size:.8125rem;font-weight:600;min-width:0;overflow:hidden;text-align:center}.zc-rg-canvas{display:flex;align-items:stretch}.zc-rg-cols{position:relative;flex:1 1 auto;display:flex;align-items:stretch;min-width:0}.zc-rg-col{position:relative;flex:1 1 0;border-left:1px solid var(--zc-border);min-width:0}.zc-rg-col>.zc-select-box{left:2px;right:2px}.zc-rg-now{z-index:4}.zc-body.zc-closed .zc-col,.zc-body.zc-closed .zc-tl-time-canvas,.zc-body.zc-closed .zc-rg-cols{background:var(--zc-nonbusiness)}.zc-event.zc-dragging{z-index:6;opacity:.9;box-shadow:0 2px 8px #0000002e}.zc-resize-handle{position:absolute;z-index:2}.zc-resize-s{left:0;right:0;bottom:0;height:6px;cursor:ns-resize}.zc-resize-e,.zc-resize-w{top:0;bottom:0;width:6px;cursor:ew-resize}.zc-resize-e{right:0}.zc-resize-w{left:0}.zc-select-box{position:absolute;z-index:5;background:var(--zc-event-bg);border:1px solid var(--zc-event-border);opacity:.45;pointer-events:none;border-radius:var(--zc-radius)}.zc-col>.zc-select-box{left:2px;right:2px}.zc-tl-select{top:2px;bottom:2px}.zc-tl-row.zc-drop-target{background:var(--zc-today-bg)}.zc-now-indicator{position:absolute;left:0;right:0;height:0;border-top:2px solid var(--zc-now);z-index:4;pointer-events:none}.zc-now-indicator:before{content:"";position:absolute;left:-1px;top:-4px;width:7px;height:7px;border-radius:50%;background:var(--zc-now)}