mglon-schedule 0.0.4
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/LICENSE +22 -0
- package/README.md +40 -0
- package/fesm2022/mglon-schedule.mjs +3800 -0
- package/fesm2022/mglon-schedule.mjs.map +1 -0
- package/package.json +44 -0
- package/types/mglon-schedule.d.ts +963 -0
|
@@ -0,0 +1,3800 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, input, computed, inject, ElementRef, PLATFORM_ID, output, signal, HostListener, Input, Directive, viewChild, afterNextRender, contentChildren, effect, Renderer2, ViewContainerRef, EventEmitter, Output, ViewEncapsulation, InjectionToken, untracked } from '@angular/core';
|
|
3
|
+
import { trigger, transition, style, animate } from '@angular/animations';
|
|
4
|
+
import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, isSameMonth, startOfDay, endOfDay, differenceInCalendarDays, addDays, differenceInDays, max, min, differenceInMilliseconds, addMilliseconds } from 'date-fns';
|
|
5
|
+
import * as i1 from '@angular/common';
|
|
6
|
+
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
|
7
|
+
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
8
|
+
import { Subject } from 'rxjs';
|
|
9
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
10
|
+
import { filter, takeUntil } from 'rxjs/operators';
|
|
11
|
+
import { RRule } from 'rrule';
|
|
12
|
+
|
|
13
|
+
class ResourceView {
|
|
14
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: ResourceView, isStandalone: true, selector: "mglon-resource-view", ngImport: i0, template: "<div class=\"ngx-cal-resource-view\">\n <!-- Header: Time/Dates (Sticky Top) -->\n <div class=\"ngx-cal-resource-view__header\">\n <div class=\"ngx-cal-resource-view__corner\"></div> <!-- Empty corner above sidebar -->\n <div class=\"ngx-cal-resource-view__time-track\">\n <!-- Mock items for now -->\n <div class=\"ngx-cal-resource-view__time-cell\">08:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">09:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">10:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">11:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">12:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">13:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">14:00</div>\n </div>\n </div>\n\n <!-- Body: Sidebar + Grid -->\n <div class=\"ngx-cal-resource-view__body\">\n <!-- Sidebar: Resources (Sticky Left) -->\n <div class=\"ngx-cal-resource-view__sidebar\">\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource A</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource B</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource C</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource D</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource E</div>\n </div>\n\n <!-- Main Grid -->\n <div class=\"ngx-cal-resource-view__grid\">\n <!-- Rows corresponding to resources -->\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n </div>\n </div>\n</div>", styles: [":host{--mglon-schedule-primary-50: #e8f0fe;--mglon-schedule-primary-100: #d2e3fc;--mglon-schedule-primary-200: #a7cbfb;--mglon-schedule-primary-300: #7cacf8;--mglon-schedule-primary-400: #518ef5;--mglon-schedule-primary-500: #1a73e8;--mglon-schedule-primary-600: #1967d2;--mglon-schedule-primary-700: #185abc;--mglon-schedule-primary-800: #174ea6;--mglon-schedule-primary-900: #0d3a86;--mglon-schedule-primary-950: #08265a;--mglon-schedule-neutral-50: #fafafa;--mglon-schedule-neutral-100: #f5f5f5;--mglon-schedule-neutral-200: #e5e5e5;--mglon-schedule-neutral-300: #d4d4d4;--mglon-schedule-neutral-400: #a3a3a3;--mglon-schedule-neutral-500: #737373;--mglon-schedule-neutral-600: #525252;--mglon-schedule-neutral-700: #404040;--mglon-schedule-neutral-800: #262626;--mglon-schedule-neutral-900: #171717;--mglon-schedule-neutral-950: #0a0a0a;--mglon-schedule-primary: var(--mglon-schedule-primary-500);--mglon-schedule-on-primary: #ffffff;--mglon-schedule-surface: #ffffff;--mglon-schedule-on-surface: var(--mglon-schedule-neutral-900);--mglon-schedule-surface-variant: var(--mglon-schedule-neutral-100);--mglon-schedule-on-surface-variant: var(--mglon-schedule-neutral-700);--mglon-schedule-background: #ffffff;--mglon-schedule-on-background: var(--mglon-schedule-neutral-900);--mglon-schedule-border: var(--mglon-schedule-neutral-200);--mglon-schedule-text-primary: var(--mglon-schedule-neutral-900);--mglon-schedule-text-secondary: var(--mglon-schedule-neutral-500);--mglon-schedule-text-tertiary: var(--mglon-schedule-neutral-400);--mglon-schedule-hover: var(--mglon-schedule-neutral-100);--mglon-schedule-selection: var(--mglon-schedule-primary-50);--mglon-schedule-error: #ea4335;--mglon-schedule-on-error: #ffffff;--mglon-schedule-spacing-xs: 4px;--mglon-schedule-spacing-sm: 8px;--mglon-schedule-spacing-md: 16px;--mglon-schedule-spacing-lg: 24px;--mglon-schedule-padding-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-padding-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-padding-md: var(--mglon-schedule-spacing-md);--mglon-schedule-padding-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-margin-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-margin-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-margin-md: var(--mglon-schedule-spacing-md);--mglon-schedule-margin-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-gap-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-gap-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-gap-md: var(--mglon-schedule-spacing-md);--mglon-schedule-gap-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-border-width-sm: 1px;--mglon-schedule-border-width-md: 2px;--mglon-schedule-border-width-lg: 4px;--mglon-schedule-header-height: 48px;--mglon-schedule-sidebar-width: 60px;--mglon-schedule-cell-height: 48px;--mglon-schedule-radius-sm: 4px;--mglon-schedule-radius-md: 8px;--mglon-schedule-radius-lg: 16px;--mglon-schedule-radius-full: 9999px;--mglon-schedule-font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;--mglon-schedule-font-size-xs: 10px;--mglon-schedule-font-size-sm: 12px;--mglon-schedule-font-size-md: 14px;--mglon-schedule-font-size-lg: 16px;--mglon-schedule-shadow-sm: 0 1px 2px rgba(0, 0, 0, .1);--mglon-schedule-shadow-md: 0 2px 4px rgba(0, 0, 0, .15);--mglon-schedule-shadow-lg: 0 4px 8px rgba(0, 0, 0, .2);--mglon-schedule-z-sticky: 10;--mglon-schedule-z-header: 20;--mglon-schedule-hover-bg: rgba(0, 0, 0, .04);--mglon-schedule-focus-ring-color: var(--mglon-schedule-primary-200);--mglon-schedule-disabled-opacity: .5;--mglon-schedule-transition-duration: .2s;--mglon-schedule-transition-easing: cubic-bezier(.4, 0, .2, 1);--mglon-schedule-month-week-display: grid;--mglon-schedule-month-week-columns: repeat(7, 1fr);--mglon-schedule-month-week-min-height: 80px;--mglon-schedule-month-week-border-bottom-width: 1px;display:block;--resource-view-bg: var(--mglon-resource-view-bg, var(--mglon-schedule-background));--resource-view-font-family: var(--mglon-resource-view-font-family, var(--mglon-schedule-font-family));--resource-view-text-color: var(--mglon-resource-view-text-color, var(--mglon-schedule-text-primary));--resource-sidebar-width: var(--mglon-resource-sidebar-width, var(--mglon-schedule-sidebar-width));--resource-header-height: var(--mglon-resource-header-height, var(--mglon-schedule-header-height));--resource-cell-height: var(--mglon-resource-cell-height, var(--mglon-schedule-cell-height));--resource-border: var(--mglon-resource-border, 1px solid var(--mglon-schedule-border));font-family:var(--resource-view-font-family);color:var(--resource-view-text-color);background-color:var(--resource-view-bg);height:100%;overflow:auto;position:relative}.ngx-cal-resource-view{display:grid;grid-template-columns:var(--resource-sidebar-width) 1fr;grid-template-rows:var(--resource-header-height) 1fr;min-width:fit-content}.ngx-cal-resource-view__header{display:contents}.ngx-cal-resource-view__corner{top:0;z-index:var(--mglon-schedule-z-header);position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky);z-index:30;grid-row:1;grid-column:1;border-right:var(--resource-border);border-bottom:var(--resource-border);background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__time-track{position:sticky;top:0;z-index:var(--mglon-schedule-z-header);grid-row:1;grid-column:2;display:flex;border-bottom:var(--resource-border);background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__time-cell{display:flex;align-items:center;justify-content:center;flex:0 0 100px;height:100%;border-right:var(--resource-border);font-size:var(--mglon-schedule-font-size-sm);color:var(--mglon-schedule-text-secondary)}.ngx-cal-resource-view__sidebar{grid-row:2;grid-column:1;display:block;background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__resource-cell{position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky);height:var(--resource-cell-height);display:flex;align-items:center;padding:0 var(--mglon-schedule-spacing-sm);border-right:var(--resource-border);border-bottom:var(--resource-border);font-size:var(--mglon-schedule-font-size-md);background-color:var(--mglon-schedule-surface);width:var(--resource-sidebar-width);box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-cal-resource-view__grid{grid-row:2;grid-column:2;display:block}.ngx-cal-resource-view__row{display:flex;height:var(--resource-cell-height);border-bottom:var(--resource-border)}.ngx-cal-resource-view__cell{flex:0 0 100px;border-right:var(--resource-border);height:100%}.ngx-cal-resource-view__cell:hover{background-color:var(--mglon-schedule-hover)}\n"] });
|
|
16
|
+
}
|
|
17
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceView, decorators: [{
|
|
18
|
+
type: Component,
|
|
19
|
+
args: [{ selector: 'mglon-resource-view', imports: [], template: "<div class=\"ngx-cal-resource-view\">\n <!-- Header: Time/Dates (Sticky Top) -->\n <div class=\"ngx-cal-resource-view__header\">\n <div class=\"ngx-cal-resource-view__corner\"></div> <!-- Empty corner above sidebar -->\n <div class=\"ngx-cal-resource-view__time-track\">\n <!-- Mock items for now -->\n <div class=\"ngx-cal-resource-view__time-cell\">08:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">09:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">10:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">11:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">12:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">13:00</div>\n <div class=\"ngx-cal-resource-view__time-cell\">14:00</div>\n </div>\n </div>\n\n <!-- Body: Sidebar + Grid -->\n <div class=\"ngx-cal-resource-view__body\">\n <!-- Sidebar: Resources (Sticky Left) -->\n <div class=\"ngx-cal-resource-view__sidebar\">\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource A</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource B</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource C</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource D</div>\n <div class=\"ngx-cal-resource-view__resource-cell\">Resource E</div>\n </div>\n\n <!-- Main Grid -->\n <div class=\"ngx-cal-resource-view__grid\">\n <!-- Rows corresponding to resources -->\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n <div class=\"ngx-cal-resource-view__row\">\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n <div class=\"ngx-cal-resource-view__cell\"></div>\n </div>\n </div>\n </div>\n</div>", styles: [":host{--mglon-schedule-primary-50: #e8f0fe;--mglon-schedule-primary-100: #d2e3fc;--mglon-schedule-primary-200: #a7cbfb;--mglon-schedule-primary-300: #7cacf8;--mglon-schedule-primary-400: #518ef5;--mglon-schedule-primary-500: #1a73e8;--mglon-schedule-primary-600: #1967d2;--mglon-schedule-primary-700: #185abc;--mglon-schedule-primary-800: #174ea6;--mglon-schedule-primary-900: #0d3a86;--mglon-schedule-primary-950: #08265a;--mglon-schedule-neutral-50: #fafafa;--mglon-schedule-neutral-100: #f5f5f5;--mglon-schedule-neutral-200: #e5e5e5;--mglon-schedule-neutral-300: #d4d4d4;--mglon-schedule-neutral-400: #a3a3a3;--mglon-schedule-neutral-500: #737373;--mglon-schedule-neutral-600: #525252;--mglon-schedule-neutral-700: #404040;--mglon-schedule-neutral-800: #262626;--mglon-schedule-neutral-900: #171717;--mglon-schedule-neutral-950: #0a0a0a;--mglon-schedule-primary: var(--mglon-schedule-primary-500);--mglon-schedule-on-primary: #ffffff;--mglon-schedule-surface: #ffffff;--mglon-schedule-on-surface: var(--mglon-schedule-neutral-900);--mglon-schedule-surface-variant: var(--mglon-schedule-neutral-100);--mglon-schedule-on-surface-variant: var(--mglon-schedule-neutral-700);--mglon-schedule-background: #ffffff;--mglon-schedule-on-background: var(--mglon-schedule-neutral-900);--mglon-schedule-border: var(--mglon-schedule-neutral-200);--mglon-schedule-text-primary: var(--mglon-schedule-neutral-900);--mglon-schedule-text-secondary: var(--mglon-schedule-neutral-500);--mglon-schedule-text-tertiary: var(--mglon-schedule-neutral-400);--mglon-schedule-hover: var(--mglon-schedule-neutral-100);--mglon-schedule-selection: var(--mglon-schedule-primary-50);--mglon-schedule-error: #ea4335;--mglon-schedule-on-error: #ffffff;--mglon-schedule-spacing-xs: 4px;--mglon-schedule-spacing-sm: 8px;--mglon-schedule-spacing-md: 16px;--mglon-schedule-spacing-lg: 24px;--mglon-schedule-padding-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-padding-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-padding-md: var(--mglon-schedule-spacing-md);--mglon-schedule-padding-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-margin-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-margin-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-margin-md: var(--mglon-schedule-spacing-md);--mglon-schedule-margin-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-gap-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-gap-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-gap-md: var(--mglon-schedule-spacing-md);--mglon-schedule-gap-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-border-width-sm: 1px;--mglon-schedule-border-width-md: 2px;--mglon-schedule-border-width-lg: 4px;--mglon-schedule-header-height: 48px;--mglon-schedule-sidebar-width: 60px;--mglon-schedule-cell-height: 48px;--mglon-schedule-radius-sm: 4px;--mglon-schedule-radius-md: 8px;--mglon-schedule-radius-lg: 16px;--mglon-schedule-radius-full: 9999px;--mglon-schedule-font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;--mglon-schedule-font-size-xs: 10px;--mglon-schedule-font-size-sm: 12px;--mglon-schedule-font-size-md: 14px;--mglon-schedule-font-size-lg: 16px;--mglon-schedule-shadow-sm: 0 1px 2px rgba(0, 0, 0, .1);--mglon-schedule-shadow-md: 0 2px 4px rgba(0, 0, 0, .15);--mglon-schedule-shadow-lg: 0 4px 8px rgba(0, 0, 0, .2);--mglon-schedule-z-sticky: 10;--mglon-schedule-z-header: 20;--mglon-schedule-hover-bg: rgba(0, 0, 0, .04);--mglon-schedule-focus-ring-color: var(--mglon-schedule-primary-200);--mglon-schedule-disabled-opacity: .5;--mglon-schedule-transition-duration: .2s;--mglon-schedule-transition-easing: cubic-bezier(.4, 0, .2, 1);--mglon-schedule-month-week-display: grid;--mglon-schedule-month-week-columns: repeat(7, 1fr);--mglon-schedule-month-week-min-height: 80px;--mglon-schedule-month-week-border-bottom-width: 1px;display:block;--resource-view-bg: var(--mglon-resource-view-bg, var(--mglon-schedule-background));--resource-view-font-family: var(--mglon-resource-view-font-family, var(--mglon-schedule-font-family));--resource-view-text-color: var(--mglon-resource-view-text-color, var(--mglon-schedule-text-primary));--resource-sidebar-width: var(--mglon-resource-sidebar-width, var(--mglon-schedule-sidebar-width));--resource-header-height: var(--mglon-resource-header-height, var(--mglon-schedule-header-height));--resource-cell-height: var(--mglon-resource-cell-height, var(--mglon-schedule-cell-height));--resource-border: var(--mglon-resource-border, 1px solid var(--mglon-schedule-border));font-family:var(--resource-view-font-family);color:var(--resource-view-text-color);background-color:var(--resource-view-bg);height:100%;overflow:auto;position:relative}.ngx-cal-resource-view{display:grid;grid-template-columns:var(--resource-sidebar-width) 1fr;grid-template-rows:var(--resource-header-height) 1fr;min-width:fit-content}.ngx-cal-resource-view__header{display:contents}.ngx-cal-resource-view__corner{top:0;z-index:var(--mglon-schedule-z-header);position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky);z-index:30;grid-row:1;grid-column:1;border-right:var(--resource-border);border-bottom:var(--resource-border);background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__time-track{position:sticky;top:0;z-index:var(--mglon-schedule-z-header);grid-row:1;grid-column:2;display:flex;border-bottom:var(--resource-border);background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__time-cell{display:flex;align-items:center;justify-content:center;flex:0 0 100px;height:100%;border-right:var(--resource-border);font-size:var(--mglon-schedule-font-size-sm);color:var(--mglon-schedule-text-secondary)}.ngx-cal-resource-view__sidebar{grid-row:2;grid-column:1;display:block;background-color:var(--mglon-schedule-surface)}.ngx-cal-resource-view__resource-cell{position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky);height:var(--resource-cell-height);display:flex;align-items:center;padding:0 var(--mglon-schedule-spacing-sm);border-right:var(--resource-border);border-bottom:var(--resource-border);font-size:var(--mglon-schedule-font-size-md);background-color:var(--mglon-schedule-surface);width:var(--resource-sidebar-width);box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-cal-resource-view__grid{grid-row:2;grid-column:2;display:block}.ngx-cal-resource-view__row{display:flex;height:var(--resource-cell-height);border-bottom:var(--resource-border)}.ngx-cal-resource-view__cell{flex:0 0 100px;border-right:var(--resource-border);height:100%}.ngx-cal-resource-view__cell:hover{background-color:var(--mglon-schedule-hover)}\n"] }]
|
|
20
|
+
}] });
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if an event is a regular Event
|
|
24
|
+
*/
|
|
25
|
+
function isEvent(event) {
|
|
26
|
+
return event.type === 'event';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Type guard to check if an event is an AllDayEvent
|
|
30
|
+
*/
|
|
31
|
+
function isAllDayEvent(event) {
|
|
32
|
+
return event.type === 'all-day';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Type guard to check if an event is a RecurrentEvent
|
|
36
|
+
*/
|
|
37
|
+
function isRecurrentEvent(event) {
|
|
38
|
+
return event.type === 'recurrent';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generates a complete month calendar grid with padding days from adjacent months.
|
|
43
|
+
* Always starts on Sunday and includes complete weeks (5-6 weeks total).
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* getMonthCalendarGrid(new Date(2024, 0, 15)) // January 2024
|
|
47
|
+
* // Returns 5 weeks: Dec 31, 2023 → Feb 3, 2024
|
|
48
|
+
*/
|
|
49
|
+
function getMonthCalendarGrid(date) {
|
|
50
|
+
const monthStart = startOfMonth(date);
|
|
51
|
+
const monthEnd = endOfMonth(date);
|
|
52
|
+
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 0 });
|
|
53
|
+
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 0 });
|
|
54
|
+
const allDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
|
55
|
+
const calendarDays = allDays.map(day => ({
|
|
56
|
+
date: day,
|
|
57
|
+
day: day.getDate(),
|
|
58
|
+
isCurrentMonth: isSameMonth(day, date),
|
|
59
|
+
isPreviousMonth: day < monthStart,
|
|
60
|
+
isNextMonth: day > monthEnd,
|
|
61
|
+
}));
|
|
62
|
+
const weeks = [];
|
|
63
|
+
for (let i = 0; i < calendarDays.length; i += 7) {
|
|
64
|
+
weeks.push({ days: calendarDays.slice(i, i + 7) });
|
|
65
|
+
}
|
|
66
|
+
return weeks;
|
|
67
|
+
}
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// WEEK VIEW - Interfaces & Functions
|
|
70
|
+
// ============================================================================
|
|
71
|
+
const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
72
|
+
/**
|
|
73
|
+
* Generates the 7 days of the week containing the given date.
|
|
74
|
+
* Week starts on Sunday.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* getWeekDays(new Date(2024, 0, 15)) // Monday, Jan 15
|
|
78
|
+
* // Returns: Sun Jan 14 → Sat Jan 20
|
|
79
|
+
*/
|
|
80
|
+
function getWeekDays(date) {
|
|
81
|
+
const weekStart = startOfWeek(date, { weekStartsOn: 0 });
|
|
82
|
+
const weekEnd = endOfWeek(date, { weekStartsOn: 0 });
|
|
83
|
+
const days = eachDayOfInterval({ start: weekStart, end: weekEnd });
|
|
84
|
+
const today = new Date();
|
|
85
|
+
today.setHours(0, 0, 0, 0);
|
|
86
|
+
return days.map(day => {
|
|
87
|
+
const dayDate = new Date(day);
|
|
88
|
+
dayDate.setHours(0, 0, 0, 0);
|
|
89
|
+
return {
|
|
90
|
+
date: dayDate,
|
|
91
|
+
dayOfWeek: day.getDay(),
|
|
92
|
+
dayName: DAY_NAMES[day.getDay()],
|
|
93
|
+
dayNumber: day.getDate(),
|
|
94
|
+
isToday: dayDate.getTime() === today.getTime()
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generates a 2D matrix of time slots for a week.
|
|
100
|
+
* Each day contains 48 slots (24h × 2 = 30-minute intervals).
|
|
101
|
+
*
|
|
102
|
+
* @returns TimeSlot[7 days][48 slots per day]
|
|
103
|
+
*/
|
|
104
|
+
function getWeekTimeSlots(date) {
|
|
105
|
+
const weekDays = getWeekDays(date);
|
|
106
|
+
const today = new Date();
|
|
107
|
+
today.setHours(0, 0, 0, 0);
|
|
108
|
+
return weekDays.map(weekDay => {
|
|
109
|
+
const slots = [];
|
|
110
|
+
for (let hour = 0; hour < 24; hour++) {
|
|
111
|
+
for (let minute = 0; minute < 60; minute += 30) {
|
|
112
|
+
const slotDate = new Date(weekDay.date);
|
|
113
|
+
slotDate.setHours(hour, minute, 0, 0);
|
|
114
|
+
slots.push({
|
|
115
|
+
date: slotDate,
|
|
116
|
+
hour,
|
|
117
|
+
minute,
|
|
118
|
+
dayOfWeek: weekDay.dayOfWeek,
|
|
119
|
+
isToday: weekDay.isToday,
|
|
120
|
+
timeLabel: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return slots;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// VIEW RANGE - Functions for calculating visible date ranges
|
|
129
|
+
// ============================================================================
|
|
130
|
+
/**
|
|
131
|
+
* Calculates the visible date range based on view mode.
|
|
132
|
+
* Used to filter which events should be displayed.
|
|
133
|
+
*/
|
|
134
|
+
function getViewRange(date, viewMode) {
|
|
135
|
+
const normalizedDate = new Date(date);
|
|
136
|
+
normalizedDate.setHours(0, 0, 0, 0);
|
|
137
|
+
switch (viewMode) {
|
|
138
|
+
case 'month': {
|
|
139
|
+
const monthStart = startOfMonth(normalizedDate);
|
|
140
|
+
const monthEnd = endOfMonth(normalizedDate);
|
|
141
|
+
return {
|
|
142
|
+
start: startOfWeek(monthStart, { weekStartsOn: 0 }),
|
|
143
|
+
end: endOfWeek(monthEnd, { weekStartsOn: 0 })
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case 'week': {
|
|
147
|
+
return {
|
|
148
|
+
start: startOfWeek(normalizedDate, { weekStartsOn: 0 }),
|
|
149
|
+
end: endOfWeek(normalizedDate, { weekStartsOn: 0 })
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
case 'day':
|
|
153
|
+
case 'resource': {
|
|
154
|
+
const endOfDay = new Date(normalizedDate);
|
|
155
|
+
endOfDay.setHours(23, 59, 59, 999);
|
|
156
|
+
return { start: normalizedDate, end: endOfDay };
|
|
157
|
+
}
|
|
158
|
+
case 'list': {
|
|
159
|
+
return {
|
|
160
|
+
start: startOfMonth(normalizedDate),
|
|
161
|
+
end: endOfMonth(normalizedDate)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
default:
|
|
165
|
+
return { start: normalizedDate, end: normalizedDate };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// EVENT FILTERING - Functions to check event visibility
|
|
170
|
+
// ============================================================================
|
|
171
|
+
/**
|
|
172
|
+
* Checks if an event overlaps with a given date range.
|
|
173
|
+
* Handles standard events, all-day events, and recurrent events.
|
|
174
|
+
*/
|
|
175
|
+
function isEventInRange(event, range) {
|
|
176
|
+
if (isEvent(event) || isRecurrentEvent(event)) {
|
|
177
|
+
return event.start <= range.end && event.end >= range.start;
|
|
178
|
+
}
|
|
179
|
+
if (isAllDayEvent(event)) {
|
|
180
|
+
const eventStart = startOfDay(event.date);
|
|
181
|
+
const eventEnd = event.endDate ? endOfDay(event.endDate) : endOfDay(event.date);
|
|
182
|
+
return eventStart <= range.end && eventEnd >= range.start;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Color manipulation utilities for dynamic theming
|
|
189
|
+
*/
|
|
190
|
+
/**
|
|
191
|
+
* Converts hex color to RGB
|
|
192
|
+
*/
|
|
193
|
+
function hexToRgb(hex) {
|
|
194
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
195
|
+
return result ? {
|
|
196
|
+
r: parseInt(result[1], 16),
|
|
197
|
+
g: parseInt(result[2], 16),
|
|
198
|
+
b: parseInt(result[3], 16)
|
|
199
|
+
} : null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Converts RGB to hex
|
|
203
|
+
*/
|
|
204
|
+
function rgbToHex(r, g, b) {
|
|
205
|
+
return "#" + [r, g, b].map(x => {
|
|
206
|
+
const hex = Math.round(x).toString(16);
|
|
207
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
208
|
+
}).join('');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Converts RGB to HSL
|
|
212
|
+
*/
|
|
213
|
+
function rgbToHsl(r, g, b) {
|
|
214
|
+
r /= 255;
|
|
215
|
+
g /= 255;
|
|
216
|
+
b /= 255;
|
|
217
|
+
const max = Math.max(r, g, b);
|
|
218
|
+
const min = Math.min(r, g, b);
|
|
219
|
+
let h = 0;
|
|
220
|
+
let s = 0;
|
|
221
|
+
const l = (max + min) / 2;
|
|
222
|
+
if (max !== min) {
|
|
223
|
+
const d = max - min;
|
|
224
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
225
|
+
switch (max) {
|
|
226
|
+
case r:
|
|
227
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
228
|
+
break;
|
|
229
|
+
case g:
|
|
230
|
+
h = ((b - r) / d + 2) / 6;
|
|
231
|
+
break;
|
|
232
|
+
case b:
|
|
233
|
+
h = ((r - g) / d + 4) / 6;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { h: h * 360, s: s * 100, l: l * 100 };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Converts HSL to RGB
|
|
241
|
+
*/
|
|
242
|
+
function hslToRgb(h, s, l) {
|
|
243
|
+
h /= 360;
|
|
244
|
+
s /= 100;
|
|
245
|
+
l /= 100;
|
|
246
|
+
let r, g, b;
|
|
247
|
+
if (s === 0) {
|
|
248
|
+
r = g = b = l;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const hue2rgb = (p, q, t) => {
|
|
252
|
+
if (t < 0)
|
|
253
|
+
t += 1;
|
|
254
|
+
if (t > 1)
|
|
255
|
+
t -= 1;
|
|
256
|
+
if (t < 1 / 6)
|
|
257
|
+
return p + (q - p) * 6 * t;
|
|
258
|
+
if (t < 1 / 2)
|
|
259
|
+
return q;
|
|
260
|
+
if (t < 2 / 3)
|
|
261
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
262
|
+
return p;
|
|
263
|
+
};
|
|
264
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
265
|
+
const p = 2 * l - q;
|
|
266
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
267
|
+
g = hue2rgb(p, q, h);
|
|
268
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
269
|
+
}
|
|
270
|
+
return { r: r * 255, g: g * 255, b: b * 255 };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Calculate relative luminance (WCAG formula)
|
|
274
|
+
*/
|
|
275
|
+
function getLuminance(r, g, b) {
|
|
276
|
+
const [rs, gs, bs] = [r, g, b].map(c => {
|
|
277
|
+
c = c / 255;
|
|
278
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
279
|
+
});
|
|
280
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Calculate contrast ratio between two colors
|
|
284
|
+
*/
|
|
285
|
+
function getContrastRatio(rgb1, rgb2) {
|
|
286
|
+
const lum1 = getLuminance(rgb1.r, rgb1.g, rgb1.b);
|
|
287
|
+
const lum2 = getLuminance(rgb2.r, rgb2.g, rgb2.b);
|
|
288
|
+
const lighter = Math.max(lum1, lum2);
|
|
289
|
+
const darker = Math.min(lum1, lum2);
|
|
290
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Generates a hover color (darker variant)
|
|
294
|
+
* If the base color is already dark, it slightly lightens instead
|
|
295
|
+
*/
|
|
296
|
+
function getHoverColor(baseColor) {
|
|
297
|
+
const rgb = hexToRgb(baseColor);
|
|
298
|
+
if (!rgb)
|
|
299
|
+
return baseColor;
|
|
300
|
+
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
|
|
301
|
+
// If color is very dark (L < 20%), lighten it
|
|
302
|
+
// Otherwise, darken it
|
|
303
|
+
if (hsl.l < 20) {
|
|
304
|
+
hsl.l = Math.min(100, hsl.l + 15);
|
|
305
|
+
hsl.s = Math.min(100, hsl.s + 10);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
hsl.l = Math.max(0, hsl.l - 12);
|
|
309
|
+
hsl.s = Math.min(100, hsl.s + 8);
|
|
310
|
+
}
|
|
311
|
+
const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
|
|
312
|
+
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Generates a very light background variant (almost white)
|
|
316
|
+
*/
|
|
317
|
+
function getLightBackgroundColor(baseColor) {
|
|
318
|
+
const rgb = hexToRgb(baseColor);
|
|
319
|
+
if (!rgb)
|
|
320
|
+
return '#f5f5f5';
|
|
321
|
+
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
|
|
322
|
+
// Keep the hue, reduce saturation, increase lightness to 95-97%
|
|
323
|
+
hsl.s = Math.min(30, hsl.s * 0.3);
|
|
324
|
+
hsl.l = 96;
|
|
325
|
+
const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
|
|
326
|
+
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Generates optimal text color (white or dark) for contrast
|
|
330
|
+
* Returns white for dark backgrounds, dark for light backgrounds
|
|
331
|
+
*/
|
|
332
|
+
function getTextColor(backgroundColor) {
|
|
333
|
+
const rgb = hexToRgb(backgroundColor);
|
|
334
|
+
if (!rgb)
|
|
335
|
+
return '#000000';
|
|
336
|
+
// Calculate luminance
|
|
337
|
+
const luminance = getLuminance(rgb.r, rgb.g, rgb.b);
|
|
338
|
+
// If background is light (luminance > 0.5), use dark text
|
|
339
|
+
// Otherwise use white text
|
|
340
|
+
if (luminance > 0.5) {
|
|
341
|
+
// For very light backgrounds, use a dark gray/color-tinted text
|
|
342
|
+
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
|
|
343
|
+
// Keep hue, high saturation, low lightness
|
|
344
|
+
const textHsl = { h: hsl.h, s: Math.min(60, hsl.s * 0.8), l: 25 };
|
|
345
|
+
const textRgb = hslToRgb(textHsl.h, textHsl.s, textHsl.l);
|
|
346
|
+
return rgbToHex(textRgb.r, textRgb.g, textRgb.b);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
return '#ffffff';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Generates a full adaptive color scheme based on a single input color.
|
|
354
|
+
* It detects the Nature of the input (Vivid, Pastel, Dark) and preserves it,
|
|
355
|
+
* but also provides a "raw" variant using the color exactly as provided.
|
|
356
|
+
*/
|
|
357
|
+
function generateAdaptiveColorScheme(baseColor) {
|
|
358
|
+
const rgb = hexToRgb(baseColor);
|
|
359
|
+
if (!rgb) {
|
|
360
|
+
const fallback = '#1a73e8';
|
|
361
|
+
return generateAdaptiveColorScheme(fallback);
|
|
362
|
+
}
|
|
363
|
+
const { h, s, l } = rgbToHsl(rgb.r, rgb.g, rgb.b);
|
|
364
|
+
// 1. Determine the "Nature" of the input color
|
|
365
|
+
let type;
|
|
366
|
+
if (l > 80)
|
|
367
|
+
type = 'pastel';
|
|
368
|
+
else if (l < 20)
|
|
369
|
+
type = 'dark'; // Only extremely dark colors are "Dark"
|
|
370
|
+
else
|
|
371
|
+
type = 'vivid';
|
|
372
|
+
// 2. Generate variants, keeping the input for its detected category
|
|
373
|
+
const variants = {};
|
|
374
|
+
// Raw (Always use original input)
|
|
375
|
+
variants.raw = generateVariant(h, s, l);
|
|
376
|
+
// Vivid
|
|
377
|
+
if (type === 'vivid') {
|
|
378
|
+
variants.vivid = variants.raw;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
variants.vivid = generateVariant(h, Math.max(s, 70), 50); // Boost to vivid
|
|
382
|
+
}
|
|
383
|
+
// Pastel
|
|
384
|
+
if (type === 'pastel') {
|
|
385
|
+
variants.pastel = generateVariant(h, s, l); // Use original
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// Soften: high lightness, moderate saturation
|
|
389
|
+
variants.pastel = generateVariant(h, Math.min(s, 65), 92);
|
|
390
|
+
}
|
|
391
|
+
// Dark
|
|
392
|
+
if (type === 'dark') {
|
|
393
|
+
variants.dark = generateVariant(h, s, l); // Use original
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// Deepen: low lightness, moderate saturation
|
|
397
|
+
variants.dark = generateVariant(h, Math.min(s, 60), 25);
|
|
398
|
+
}
|
|
399
|
+
return variants;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Internal helper to generate a specific variant with its hover and text colors
|
|
403
|
+
*/
|
|
404
|
+
function generateVariant(h, s, l) {
|
|
405
|
+
const base = hslToHex(h, s, l);
|
|
406
|
+
// Hover: slightly more saturated and darker (or lighter if base is very dark)
|
|
407
|
+
const hoverL = l > 20 ? Math.max(0, l - 8) : l + 15;
|
|
408
|
+
const hoverS = Math.min(100, s + 5);
|
|
409
|
+
const hover = hslToHex(h, hoverS, hoverL);
|
|
410
|
+
return {
|
|
411
|
+
base,
|
|
412
|
+
hover,
|
|
413
|
+
text: getTextColor(base),
|
|
414
|
+
textHover: getTextColor(hover)
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Simple HSL to Hex bridge
|
|
419
|
+
*/
|
|
420
|
+
function hslToHex(h, s, l) {
|
|
421
|
+
const rgb = hslToRgb(h, s, l);
|
|
422
|
+
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Resolves the effective color for an event
|
|
426
|
+
*/
|
|
427
|
+
function getEventColor(event, getResource, defaultColor) {
|
|
428
|
+
if (event.color)
|
|
429
|
+
return event.color;
|
|
430
|
+
if (event.resourceId) {
|
|
431
|
+
const resource = getResource(event.resourceId);
|
|
432
|
+
if (resource?.color)
|
|
433
|
+
return resource.color;
|
|
434
|
+
}
|
|
435
|
+
return defaultColor;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Header component for the week view displaying the 7 days of the week.
|
|
440
|
+
*
|
|
441
|
+
* Shows day names and numbers in a sticky header that remains visible
|
|
442
|
+
* when scrolling through time slots.
|
|
443
|
+
*
|
|
444
|
+
* Features:
|
|
445
|
+
* - Sticky positioning
|
|
446
|
+
* - Highlights today
|
|
447
|
+
* - Shows day name (Sun, Mon, etc.) and day number
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```html
|
|
451
|
+
* <mglon-week-header [currentDate]="selectedDate"></mglon-week-header>
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
class WeekHeader {
|
|
455
|
+
/**
|
|
456
|
+
* The date to display the week for.
|
|
457
|
+
* Can be any date within the target week.
|
|
458
|
+
*/
|
|
459
|
+
currentDate = input(new Date(), ...(ngDevMode ? [{ debugName: "currentDate" }] : []));
|
|
460
|
+
/**
|
|
461
|
+
* Computed array of the 7 days in the week
|
|
462
|
+
*/
|
|
463
|
+
weekDays = computed(() => {
|
|
464
|
+
return getWeekDays(this.currentDate());
|
|
465
|
+
}, ...(ngDevMode ? [{ debugName: "weekDays" }] : []));
|
|
466
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
467
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: WeekHeader, isStandalone: true, selector: "mglon-week-header", inputs: { currentDate: { classPropertyName: "currentDate", publicName: "currentDate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"mglon-week-header\">\n <!-- Time column spacer -->\n <div class=\"mglon-week-header__time-column\"></div>\n\n <!-- Day columns -->\n @for (day of weekDays(); track day.date.getTime()) {\n <div class=\"mglon-week-header__day\" [class.mglon-week-header__day--today]=\"day.isToday\">\n <div class=\"mglon-week-header__day-name\">{{ day.dayName }}</div>\n <div class=\"mglon-week-header__day-number\">\n <span>{{ day.dayNumber }}</span>\n </div>\n </div>\n }\n</div>", styles: [".mglon-week-header{--week-header-time-col-width: var(--mglon-week-header-time-column-width, 60px);--week-header-bg: var(--mglon-week-header-bg, var(--mglon-schedule-surface));--week-header-border: var(--mglon-week-header-border, 2px solid var(--mglon-schedule-border-color));--week-header-scrollbar-width: var(--mglon-week-header-scrollbar-width, 8px);--week-header-z-index: var(--mglon-week-header-z-index, 10);--week-header-day-padding: var(--mglon-week-header-day-padding, 8px 4px);--week-header-day-name-size: var(--mglon-week-header-day-name-size, 11px);--week-header-day-name-color: var(--mglon-week-header-day-name-color, var(--mglon-schedule-on-surface-variant));--week-header-day-number-size: var(--mglon-week-header-day-number-size, 16px);--week-header-day-number-color: var(--mglon-week-header-day-number-color, var(--mglon-schedule-on-surface));--week-header-today-bg: var(--mglon-week-header-today-bg, var(--mglon-schedule-error));--week-header-today-color: var(--mglon-week-header-today-color, var(--mglon-schedule-on-error));--week-header-today-size: var(--mglon-week-header-today-size, 24px);display:grid;grid-template-columns:var(--week-header-time-col-width) repeat(7,1fr);position:sticky;top:0;z-index:var(--week-header-z-index);background:var(--week-header-bg);border-bottom:var(--week-header-border);padding-right:var(--week-header-scrollbar-width)}.mglon-week-header__time-column{border-right:1px solid var(--mglon-schedule-border-color)}.mglon-week-header__day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--week-header-day-padding);border-right:1px solid var(--mglon-schedule-border-color)}.mglon-week-header__day--today{position:relative}.mglon-week-header__day--today .mglon-week-header__day-number{background:var(--week-header-today-bg);color:var(--week-header-today-color);width:var(--week-header-today-size);height:var(--week-header-today-size);display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:500;position:relative;border-radius:50% 50% 50% 0;transform:rotate(-45deg)}.mglon-week-header__day--today .mglon-week-header__day-number span{display:block;transform:rotate(45deg)}.mglon-week-header__day-name{font-size:var(--week-header-day-name-size);font-weight:500;color:var(--week-header-day-name-color);text-transform:uppercase;margin-bottom:4px}.mglon-week-header__day-number{font-size:var(--week-header-day-number-size);font-weight:400;color:var(--week-header-day-number-color)}\n"] });
|
|
468
|
+
}
|
|
469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekHeader, decorators: [{
|
|
470
|
+
type: Component,
|
|
471
|
+
args: [{ selector: 'mglon-week-header', standalone: true, imports: [], template: "<div class=\"mglon-week-header\">\n <!-- Time column spacer -->\n <div class=\"mglon-week-header__time-column\"></div>\n\n <!-- Day columns -->\n @for (day of weekDays(); track day.date.getTime()) {\n <div class=\"mglon-week-header__day\" [class.mglon-week-header__day--today]=\"day.isToday\">\n <div class=\"mglon-week-header__day-name\">{{ day.dayName }}</div>\n <div class=\"mglon-week-header__day-number\">\n <span>{{ day.dayNumber }}</span>\n </div>\n </div>\n }\n</div>", styles: [".mglon-week-header{--week-header-time-col-width: var(--mglon-week-header-time-column-width, 60px);--week-header-bg: var(--mglon-week-header-bg, var(--mglon-schedule-surface));--week-header-border: var(--mglon-week-header-border, 2px solid var(--mglon-schedule-border-color));--week-header-scrollbar-width: var(--mglon-week-header-scrollbar-width, 8px);--week-header-z-index: var(--mglon-week-header-z-index, 10);--week-header-day-padding: var(--mglon-week-header-day-padding, 8px 4px);--week-header-day-name-size: var(--mglon-week-header-day-name-size, 11px);--week-header-day-name-color: var(--mglon-week-header-day-name-color, var(--mglon-schedule-on-surface-variant));--week-header-day-number-size: var(--mglon-week-header-day-number-size, 16px);--week-header-day-number-color: var(--mglon-week-header-day-number-color, var(--mglon-schedule-on-surface));--week-header-today-bg: var(--mglon-week-header-today-bg, var(--mglon-schedule-error));--week-header-today-color: var(--mglon-week-header-today-color, var(--mglon-schedule-on-error));--week-header-today-size: var(--mglon-week-header-today-size, 24px);display:grid;grid-template-columns:var(--week-header-time-col-width) repeat(7,1fr);position:sticky;top:0;z-index:var(--week-header-z-index);background:var(--week-header-bg);border-bottom:var(--week-header-border);padding-right:var(--week-header-scrollbar-width)}.mglon-week-header__time-column{border-right:1px solid var(--mglon-schedule-border-color)}.mglon-week-header__day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--week-header-day-padding);border-right:1px solid var(--mglon-schedule-border-color)}.mglon-week-header__day--today{position:relative}.mglon-week-header__day--today .mglon-week-header__day-number{background:var(--week-header-today-bg);color:var(--week-header-today-color);width:var(--week-header-today-size);height:var(--week-header-today-size);display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:500;position:relative;border-radius:50% 50% 50% 0;transform:rotate(-45deg)}.mglon-week-header__day--today .mglon-week-header__day-number span{display:block;transform:rotate(45deg)}.mglon-week-header__day-name{font-size:var(--week-header-day-name-size);font-weight:500;color:var(--week-header-day-name-color);text-transform:uppercase;margin-bottom:4px}.mglon-week-header__day-number{font-size:var(--week-header-day-number-size);font-weight:400;color:var(--week-header-day-number-color)}\n"] }]
|
|
472
|
+
}], propDecorators: { currentDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentDate", required: false }] }] } });
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Component representing a single 30-minute time slot in the week view.
|
|
476
|
+
*
|
|
477
|
+
* Displays the time label (e.g., "09:00") only for the first column (time gutter).
|
|
478
|
+
* Each slot has a minimum height of 50px and can contain events.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```html
|
|
482
|
+
* <mglon-time-slot
|
|
483
|
+
* [slot]="timeSlot"
|
|
484
|
+
* [showTimeLabel]="true">
|
|
485
|
+
* </mglon-time-slot>
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
class TimeSlotComponent {
|
|
489
|
+
/**
|
|
490
|
+
* The time slot data to display
|
|
491
|
+
*/
|
|
492
|
+
slot = input.required(...(ngDevMode ? [{ debugName: "slot" }] : []));
|
|
493
|
+
/**
|
|
494
|
+
* Whether to show the time label (only true for first column - time gutter)
|
|
495
|
+
*/
|
|
496
|
+
showTimeLabel = input(false, ...(ngDevMode ? [{ debugName: "showTimeLabel" }] : []));
|
|
497
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TimeSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
498
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TimeSlotComponent, isStandalone: true, selector: "mglon-time-slot", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: true, transformFunction: null }, showTimeLabel: { classPropertyName: "showTimeLabel", publicName: "showTimeLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"mglon-time-slot\" [class.mglon-time-slot--hour]=\"slot().minute === 0\"\n [class.mglon-time-slot--today]=\"slot().isToday\">\n @if (showTimeLabel()) {\n <span class=\"mglon-time-slot__time-label\">{{ slot().timeLabel }}</span>\n }\n</div>", styles: [".mglon-time-slot{--time-slot-height: var(--mglon-time-slot-height, 50px);--time-slot-border-color: var(--mglon-time-slot-border-color, var(--mglon-schedule-border-color));--time-slot-border-col-light: var(--mglon-time-slot-light-border-color, var(--mglon-schedule-border-color-light, #f0f0f0));--time-slot-bg: var(--mglon-time-slot-bg, var(--mglon-schedule-surface));--time-slot-label-color: var(--mglon-time-slot-label-color, var(--mglon-schedule-on-surface-variant));--time-slot-label-size: var(--mglon-time-slot-label-size, 12px);min-height:var(--time-slot-height);border-right:1px solid var(--time-slot-border-color);border-bottom:1px solid var(--time-slot-border-col-light);position:relative;background:var(--time-slot-bg)}.mglon-time-slot__time-label{position:absolute;top:-8px;right:8px;font-size:var(--time-slot-label-size);color:var(--time-slot-label-color);background:var(--time-slot-bg);padding:0 4px}\n"] });
|
|
499
|
+
}
|
|
500
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TimeSlotComponent, decorators: [{
|
|
501
|
+
type: Component,
|
|
502
|
+
args: [{ selector: 'mglon-time-slot', standalone: true, imports: [], template: "<div class=\"mglon-time-slot\" [class.mglon-time-slot--hour]=\"slot().minute === 0\"\n [class.mglon-time-slot--today]=\"slot().isToday\">\n @if (showTimeLabel()) {\n <span class=\"mglon-time-slot__time-label\">{{ slot().timeLabel }}</span>\n }\n</div>", styles: [".mglon-time-slot{--time-slot-height: var(--mglon-time-slot-height, 50px);--time-slot-border-color: var(--mglon-time-slot-border-color, var(--mglon-schedule-border-color));--time-slot-border-col-light: var(--mglon-time-slot-light-border-color, var(--mglon-schedule-border-color-light, #f0f0f0));--time-slot-bg: var(--mglon-time-slot-bg, var(--mglon-schedule-surface));--time-slot-label-color: var(--mglon-time-slot-label-color, var(--mglon-schedule-on-surface-variant));--time-slot-label-size: var(--mglon-time-slot-label-size, 12px);min-height:var(--time-slot-height);border-right:1px solid var(--time-slot-border-color);border-bottom:1px solid var(--time-slot-border-col-light);position:relative;background:var(--time-slot-bg)}.mglon-time-slot__time-label{position:absolute;top:-8px;right:8px;font-size:var(--time-slot-label-size);color:var(--time-slot-label-color);background:var(--time-slot-bg);padding:0 4px}\n"] }]
|
|
503
|
+
}], propDecorators: { slot: [{ type: i0.Input, args: [{ isSignal: true, alias: "slot", required: true }] }], showTimeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimeLabel", required: false }] }] } });
|
|
504
|
+
|
|
505
|
+
class Selection {
|
|
506
|
+
// Input for the selection corners (null means no selection)
|
|
507
|
+
selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
508
|
+
// Input to control visibility (only show while actively selecting)
|
|
509
|
+
isSelecting = input(false, ...(ngDevMode ? [{ debugName: "isSelecting" }] : []));
|
|
510
|
+
// Computed style for the indicator based on selection corners
|
|
511
|
+
indicatorStyle = computed(() => {
|
|
512
|
+
const sel = this.selection();
|
|
513
|
+
const selecting = this.isSelecting();
|
|
514
|
+
if (!sel || !selecting) {
|
|
515
|
+
return { display: 'none' };
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
top: `${sel.top}px`,
|
|
519
|
+
left: `${sel.left}px`,
|
|
520
|
+
width: `${sel.right - sel.left}px`,
|
|
521
|
+
height: `${sel.bottom - sel.top}px`,
|
|
522
|
+
display: 'block'
|
|
523
|
+
};
|
|
524
|
+
}, ...(ngDevMode ? [{ debugName: "indicatorStyle" }] : []));
|
|
525
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Selection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
526
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: Selection, isStandalone: true, selector: "mglon-selection", inputs: { selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, isSelecting: { classPropertyName: "isSelecting", publicName: "isSelecting", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"mglon-selection__container\">\n <span class=\"mglon-selection__indicator\" [ngStyle]=\"indicatorStyle()\"></span>\n</div>", styles: [":host{display:block;position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mglon-selection__container{position:relative;width:100%;height:100%}.mglon-selection__indicator{position:absolute;pointer-events:none;background-color:var(--mglon-selection-background, color-mix(in srgb, var(--mglon-schedule-primary-900, #1a237e) 30%, transparent));background-image:repeating-linear-gradient(to right,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to right,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to bottom,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to bottom,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px);background-size:100% var(--mglon-selection-border-width, 2px),100% var(--mglon-selection-border-width, 2px),var(--mglon-selection-border-width, 2px) 100%,var(--mglon-selection-border-width, 2px) 100%;background-position:0 0,0 100%,0 0,100% 0;background-repeat:no-repeat}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
527
|
+
}
|
|
528
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Selection, decorators: [{
|
|
529
|
+
type: Component,
|
|
530
|
+
args: [{ selector: 'mglon-selection', standalone: true, imports: [CommonModule], template: "<div class=\"mglon-selection__container\">\n <span class=\"mglon-selection__indicator\" [ngStyle]=\"indicatorStyle()\"></span>\n</div>", styles: [":host{display:block;position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mglon-selection__container{position:relative;width:100%;height:100%}.mglon-selection__indicator{position:absolute;pointer-events:none;background-color:var(--mglon-selection-background, color-mix(in srgb, var(--mglon-schedule-primary-900, #1a237e) 30%, transparent));background-image:repeating-linear-gradient(to right,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to right,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to bottom,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px),repeating-linear-gradient(to bottom,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 0,var(--mglon-selection-border-color, var(--mglon-schedule-primary)) 12px,transparent 12px,transparent 20px);background-size:100% var(--mglon-selection-border-width, 2px),100% var(--mglon-selection-border-width, 2px),var(--mglon-selection-border-width, 2px) 100%,var(--mglon-selection-border-width, 2px) 100%;background-position:0 0,0 100%,0 0,100% 0;background-repeat:no-repeat}\n"] }]
|
|
531
|
+
}], propDecorators: { selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], isSelecting: [{ type: i0.Input, args: [{ isSignal: true, alias: "isSelecting", required: false }] }] } });
|
|
532
|
+
|
|
533
|
+
const CELL_HEADER_HEIGHT = 24;
|
|
534
|
+
/** Height of each event slot row in pixels */
|
|
535
|
+
const SLOT_HEIGHT = 20;
|
|
536
|
+
/** Vertical gap between event slots in pixels */
|
|
537
|
+
const SLOT_GAP = 2;
|
|
538
|
+
/** Default number of visible event rows in month view */
|
|
539
|
+
const DEFAULT_VISIBLE_EVENT_ROWS = 3;
|
|
540
|
+
const DEFAULT_CONFIG = {
|
|
541
|
+
initialDate: new Date(),
|
|
542
|
+
initialView: 'month',
|
|
543
|
+
views: ['month', 'week', 'day', 'resource'],
|
|
544
|
+
slotDuration: 30,
|
|
545
|
+
dayStartHour: 0,
|
|
546
|
+
dayEndHour: 23,
|
|
547
|
+
resourceSidebarWidth: 200,
|
|
548
|
+
allowOverlaps: true,
|
|
549
|
+
locale: 'en',
|
|
550
|
+
weekStartsOn: 0,
|
|
551
|
+
theme: 'theme-light', // Asumimos un tema claro por defecto
|
|
552
|
+
height: '100%',
|
|
553
|
+
visibleEventRows: DEFAULT_VISIBLE_EVENT_ROWS,
|
|
554
|
+
backgroundSelection: true,
|
|
555
|
+
showNowIndicator: true,
|
|
556
|
+
editable: true,
|
|
557
|
+
resizableEvents: true,
|
|
558
|
+
showSidebar: true
|
|
559
|
+
};
|
|
560
|
+
const DEFAULT_RESOURCE_INPUTS = {
|
|
561
|
+
tags: [],
|
|
562
|
+
isReadOnly: false,
|
|
563
|
+
isBlocked: false,
|
|
564
|
+
isActive: true,
|
|
565
|
+
resizableEvents: true
|
|
566
|
+
};
|
|
567
|
+
const DEFAULT_EVENT_INPUTS = {
|
|
568
|
+
color: '#3788d8',
|
|
569
|
+
allDay: false,
|
|
570
|
+
description: '',
|
|
571
|
+
data: null,
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* UI Configuration Types
|
|
576
|
+
*
|
|
577
|
+
* Type definitions organized by functional areas (Header, Sidebar, Grid)
|
|
578
|
+
* for controlling the visual appearance of schedule components.
|
|
579
|
+
*/
|
|
580
|
+
/**
|
|
581
|
+
* Default UI configuration
|
|
582
|
+
*/
|
|
583
|
+
const DEFAULT_UI_CONFIG = {
|
|
584
|
+
header: {
|
|
585
|
+
buttonGroup: {
|
|
586
|
+
appearance: 'solid',
|
|
587
|
+
rounded: 'md',
|
|
588
|
+
density: 'comfortable'
|
|
589
|
+
},
|
|
590
|
+
iconButtons: {
|
|
591
|
+
rounded: 'md'
|
|
592
|
+
},
|
|
593
|
+
todayButton: {
|
|
594
|
+
rounded: 'md',
|
|
595
|
+
appearance: 'ghost'
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
sidebar: {
|
|
599
|
+
resourceItems: {
|
|
600
|
+
rounded: 'sm',
|
|
601
|
+
density: 'comfortable'
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
grid: {
|
|
605
|
+
eventSlots: {
|
|
606
|
+
rounded: 'sm'
|
|
607
|
+
},
|
|
608
|
+
overflowIndicator: {
|
|
609
|
+
rounded: 'sm',
|
|
610
|
+
appearance: 'ghost'
|
|
611
|
+
},
|
|
612
|
+
useDynamicColors: true
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const initialCalendarState = {
|
|
617
|
+
currentDate: new Date(),
|
|
618
|
+
viewMode: 'month',
|
|
619
|
+
config: DEFAULT_CONFIG,
|
|
620
|
+
uiConfig: DEFAULT_UI_CONFIG,
|
|
621
|
+
events: new Map(),
|
|
622
|
+
resources: new Map(),
|
|
623
|
+
weekRowHeight: 0,
|
|
624
|
+
expandedWeekIndex: null,
|
|
625
|
+
dragState: {
|
|
626
|
+
eventId: null,
|
|
627
|
+
grabDate: null,
|
|
628
|
+
hoverDate: null
|
|
629
|
+
},
|
|
630
|
+
resizeState: {
|
|
631
|
+
eventId: null,
|
|
632
|
+
side: null,
|
|
633
|
+
hoverDate: null
|
|
634
|
+
},
|
|
635
|
+
interactionMode: 'none',
|
|
636
|
+
hoveredEventId: null
|
|
637
|
+
};
|
|
638
|
+
const CalendarStore = signalStore({ providedIn: 'root' }, withState(initialCalendarState), withComputed(({ resources, events, currentDate, viewMode, config }) => ({
|
|
639
|
+
// Date & View Computeds
|
|
640
|
+
viewDate: computed(() => currentDate()),
|
|
641
|
+
currentView: computed(() => viewMode()),
|
|
642
|
+
viewRange: computed(() => getViewRange(currentDate(), viewMode())),
|
|
643
|
+
formattedDate: computed(() => {
|
|
644
|
+
const date = currentDate();
|
|
645
|
+
return new Intl.DateTimeFormat(config().locale, {
|
|
646
|
+
month: 'long',
|
|
647
|
+
year: 'numeric',
|
|
648
|
+
day: viewMode() === 'day' ? 'numeric' : undefined
|
|
649
|
+
}).format(date);
|
|
650
|
+
}),
|
|
651
|
+
// Resource Computeds
|
|
652
|
+
allResources: computed(() => Array.from(resources().values())),
|
|
653
|
+
resourceCount: computed(() => resources().size),
|
|
654
|
+
activeResources: computed(() => Array.from(resources().values()).filter(r => r.isActive !== false)),
|
|
655
|
+
inactiveResources: computed(() => Array.from(resources().values()).filter(r => r.isActive === false)),
|
|
656
|
+
// Event Computeds
|
|
657
|
+
allEvents: computed(() => Array.from(events().values())),
|
|
658
|
+
eventCount: computed(() => events().size),
|
|
659
|
+
currentViewEvents: computed(() => {
|
|
660
|
+
const range = getViewRange(currentDate(), viewMode());
|
|
661
|
+
const resMap = resources();
|
|
662
|
+
return Array.from(events().values()).filter(event => {
|
|
663
|
+
// Exclude 'recurrent' templates from rendering.
|
|
664
|
+
// Their expanded instances ('event' type) will be included instead.
|
|
665
|
+
if (event.type === 'recurrent')
|
|
666
|
+
return false;
|
|
667
|
+
const inRange = isEventInRange(event, range);
|
|
668
|
+
if (!inRange)
|
|
669
|
+
return false;
|
|
670
|
+
if (event.resourceId) {
|
|
671
|
+
const res = resMap.get(event.resourceId);
|
|
672
|
+
return res?.isActive !== false;
|
|
673
|
+
}
|
|
674
|
+
return true;
|
|
675
|
+
});
|
|
676
|
+
}),
|
|
677
|
+
/**
|
|
678
|
+
* Minimum height for a week row in month view.
|
|
679
|
+
* Calculated from: CELL_HEADER_HEIGHT + (n * SLOT_HEIGHT) + ((n-1) * SLOT_GAP)
|
|
680
|
+
* where n = visibleEventRows from config.
|
|
681
|
+
*/
|
|
682
|
+
minWeekRowHeight: computed(() => {
|
|
683
|
+
const rows = config().visibleEventRows ?? DEFAULT_VISIBLE_EVENT_ROWS;
|
|
684
|
+
// Formula: header + (rows * slot height) + (rows * slot gap)
|
|
685
|
+
// The last gap serves as bottom padding for the row.
|
|
686
|
+
return CELL_HEADER_HEIGHT + (rows * SLOT_HEIGHT) + (rows * SLOT_GAP);
|
|
687
|
+
})
|
|
688
|
+
})), withMethods((store) => {
|
|
689
|
+
const interaction$ = new Subject();
|
|
690
|
+
return {
|
|
691
|
+
/** Returns the observable stream of all event interactions */
|
|
692
|
+
getInteractions() {
|
|
693
|
+
return interaction$.asObservable();
|
|
694
|
+
},
|
|
695
|
+
/** Dispatches a new interaction event to all subscribers */
|
|
696
|
+
dispatchInteraction(type, eventId, payload) {
|
|
697
|
+
interaction$.next({ type, eventId, payload });
|
|
698
|
+
},
|
|
699
|
+
// --- Navigation & View Methods ---
|
|
700
|
+
setDate(date) {
|
|
701
|
+
patchState(store, { currentDate: date });
|
|
702
|
+
},
|
|
703
|
+
changeView(view) {
|
|
704
|
+
patchState(store, { viewMode: view });
|
|
705
|
+
},
|
|
706
|
+
updateConfig(config) {
|
|
707
|
+
patchState(store, (state) => ({
|
|
708
|
+
config: { ...state.config, ...config },
|
|
709
|
+
currentDate: config.initialDate ? new Date(config.initialDate) : state.currentDate,
|
|
710
|
+
viewMode: config.initialView || state.viewMode
|
|
711
|
+
}));
|
|
712
|
+
if (config.resources) {
|
|
713
|
+
this.registerResources(config.resources);
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
next() {
|
|
717
|
+
const mode = store.viewMode();
|
|
718
|
+
const current = new Date(store.currentDate());
|
|
719
|
+
switch (mode) {
|
|
720
|
+
case 'day':
|
|
721
|
+
case 'resource':
|
|
722
|
+
current.setDate(current.getDate() + 1);
|
|
723
|
+
break;
|
|
724
|
+
case 'week':
|
|
725
|
+
current.setDate(current.getDate() + 7);
|
|
726
|
+
break;
|
|
727
|
+
case 'month':
|
|
728
|
+
current.setMonth(current.getMonth() + 1);
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
patchState(store, { currentDate: current });
|
|
732
|
+
},
|
|
733
|
+
prev() {
|
|
734
|
+
const mode = store.viewMode();
|
|
735
|
+
const current = new Date(store.currentDate());
|
|
736
|
+
switch (mode) {
|
|
737
|
+
case 'day':
|
|
738
|
+
case 'resource':
|
|
739
|
+
current.setDate(current.getDate() - 1);
|
|
740
|
+
break;
|
|
741
|
+
case 'week':
|
|
742
|
+
current.setDate(current.getDate() - 7);
|
|
743
|
+
break;
|
|
744
|
+
case 'month':
|
|
745
|
+
current.setMonth(current.getMonth() - 1);
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
patchState(store, { currentDate: current });
|
|
749
|
+
},
|
|
750
|
+
today() {
|
|
751
|
+
patchState(store, { currentDate: new Date() });
|
|
752
|
+
},
|
|
753
|
+
// --- Resource Methods ---
|
|
754
|
+
registerResources(resources) {
|
|
755
|
+
patchState(store, (state) => {
|
|
756
|
+
const newMap = new Map(state.resources);
|
|
757
|
+
resources.forEach(r => newMap.set(r.id, r));
|
|
758
|
+
return { resources: newMap };
|
|
759
|
+
});
|
|
760
|
+
},
|
|
761
|
+
registerResource(resource) {
|
|
762
|
+
patchState(store, (state) => {
|
|
763
|
+
const newMap = new Map(state.resources);
|
|
764
|
+
newMap.set(resource.id, resource);
|
|
765
|
+
return { resources: newMap };
|
|
766
|
+
});
|
|
767
|
+
},
|
|
768
|
+
updateResource(id, partial) {
|
|
769
|
+
patchState(store, (state) => {
|
|
770
|
+
const resource = state.resources.get(id);
|
|
771
|
+
if (!resource)
|
|
772
|
+
return state;
|
|
773
|
+
const newMap = new Map(state.resources);
|
|
774
|
+
newMap.set(id, { ...resource, ...partial });
|
|
775
|
+
return { resources: newMap };
|
|
776
|
+
});
|
|
777
|
+
},
|
|
778
|
+
unregisterResource(id) {
|
|
779
|
+
patchState(store, (state) => {
|
|
780
|
+
const newMap = new Map(state.resources);
|
|
781
|
+
newMap.delete(id);
|
|
782
|
+
return { resources: newMap };
|
|
783
|
+
});
|
|
784
|
+
},
|
|
785
|
+
getResource(id) {
|
|
786
|
+
return store.resources().get(id);
|
|
787
|
+
},
|
|
788
|
+
toggleResource(id) {
|
|
789
|
+
const resource = store.resources().get(id);
|
|
790
|
+
if (resource) {
|
|
791
|
+
this.updateResource(id, { isActive: resource.isActive === false });
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
showResource(id) {
|
|
795
|
+
this.updateResource(id, { isActive: true });
|
|
796
|
+
},
|
|
797
|
+
hideResource(id) {
|
|
798
|
+
this.updateResource(id, { isActive: false });
|
|
799
|
+
},
|
|
800
|
+
// --- Event Methods ---
|
|
801
|
+
setEvents(events) {
|
|
802
|
+
const eventsMap = new Map();
|
|
803
|
+
events.forEach(e => eventsMap.set(e.id, e));
|
|
804
|
+
patchState(store, { events: eventsMap });
|
|
805
|
+
},
|
|
806
|
+
registerEvent(event) {
|
|
807
|
+
patchState(store, (state) => {
|
|
808
|
+
const newMap = new Map(state.events);
|
|
809
|
+
newMap.set(event.id, event);
|
|
810
|
+
return { events: newMap };
|
|
811
|
+
});
|
|
812
|
+
},
|
|
813
|
+
updateEvent(id, partial) {
|
|
814
|
+
patchState(store, (state) => {
|
|
815
|
+
const event = state.events.get(id);
|
|
816
|
+
if (!event)
|
|
817
|
+
return state;
|
|
818
|
+
const newMap = new Map(state.events);
|
|
819
|
+
newMap.set(id, { ...event, ...partial });
|
|
820
|
+
return { events: newMap };
|
|
821
|
+
});
|
|
822
|
+
},
|
|
823
|
+
unregisterEvent(id) {
|
|
824
|
+
patchState(store, (state) => {
|
|
825
|
+
const newMap = new Map(state.events);
|
|
826
|
+
newMap.delete(id);
|
|
827
|
+
return { events: newMap };
|
|
828
|
+
});
|
|
829
|
+
},
|
|
830
|
+
getEvent(id) {
|
|
831
|
+
return store.events().get(id);
|
|
832
|
+
},
|
|
833
|
+
getEventsByResource(resourceId) {
|
|
834
|
+
return Array.from(store.events().values()).filter(event => event.resourceId === resourceId);
|
|
835
|
+
},
|
|
836
|
+
clear() {
|
|
837
|
+
patchState(store, {
|
|
838
|
+
events: new Map(),
|
|
839
|
+
resources: new Map(),
|
|
840
|
+
});
|
|
841
|
+
},
|
|
842
|
+
setUIConfig(config) {
|
|
843
|
+
patchState(store, (state) => ({
|
|
844
|
+
uiConfig: {
|
|
845
|
+
header: config.header
|
|
846
|
+
? {
|
|
847
|
+
buttonGroup: config.header.buttonGroup
|
|
848
|
+
? { ...state.uiConfig.header.buttonGroup, ...config.header.buttonGroup }
|
|
849
|
+
: state.uiConfig.header.buttonGroup,
|
|
850
|
+
iconButtons: config.header.iconButtons
|
|
851
|
+
? { ...state.uiConfig.header.iconButtons, ...config.header.iconButtons }
|
|
852
|
+
: state.uiConfig.header.iconButtons,
|
|
853
|
+
todayButton: config.header.todayButton
|
|
854
|
+
? { ...state.uiConfig.header.todayButton, ...config.header.todayButton }
|
|
855
|
+
: state.uiConfig.header.todayButton
|
|
856
|
+
}
|
|
857
|
+
: state.uiConfig.header,
|
|
858
|
+
sidebar: config.sidebar
|
|
859
|
+
? {
|
|
860
|
+
resourceItems: config.sidebar.resourceItems
|
|
861
|
+
? { ...state.uiConfig.sidebar.resourceItems, ...config.sidebar.resourceItems }
|
|
862
|
+
: state.uiConfig.sidebar.resourceItems,
|
|
863
|
+
background: config.sidebar.background !== undefined
|
|
864
|
+
? config.sidebar.background
|
|
865
|
+
: state.uiConfig.sidebar.background,
|
|
866
|
+
rounded: config.sidebar.rounded !== undefined
|
|
867
|
+
? config.sidebar.rounded
|
|
868
|
+
: state.uiConfig.sidebar.rounded
|
|
869
|
+
}
|
|
870
|
+
: state.uiConfig.sidebar,
|
|
871
|
+
grid: config.grid
|
|
872
|
+
? {
|
|
873
|
+
eventSlots: config.grid.eventSlots
|
|
874
|
+
? { ...state.uiConfig.grid.eventSlots, ...config.grid.eventSlots }
|
|
875
|
+
: state.uiConfig.grid.eventSlots,
|
|
876
|
+
overflowIndicator: config.grid.overflowIndicator
|
|
877
|
+
? { ...state.uiConfig.grid.overflowIndicator, ...config.grid.overflowIndicator }
|
|
878
|
+
: state.uiConfig.grid.overflowIndicator,
|
|
879
|
+
useDynamicColors: config.grid.useDynamicColors !== undefined
|
|
880
|
+
? config.grid.useDynamicColors
|
|
881
|
+
: state.uiConfig.grid.useDynamicColors
|
|
882
|
+
}
|
|
883
|
+
: state.uiConfig.grid
|
|
884
|
+
}
|
|
885
|
+
}));
|
|
886
|
+
},
|
|
887
|
+
/**
|
|
888
|
+
* Sets the height of a week row container.
|
|
889
|
+
* Called once from the first week's ResizeObserver.
|
|
890
|
+
*/
|
|
891
|
+
setWeekRowHeight(height) {
|
|
892
|
+
patchState(store, { weekRowHeight: height });
|
|
893
|
+
},
|
|
894
|
+
/**
|
|
895
|
+
* Toggles expansion of a week. Only one week can be expanded at a time.
|
|
896
|
+
* If the same week is toggled twice, it collapses.
|
|
897
|
+
* @param weekIndex - The index of the week to toggle (0-5)
|
|
898
|
+
*/
|
|
899
|
+
toggleWeekExpansion(weekIndex) {
|
|
900
|
+
const current = store.expandedWeekIndex();
|
|
901
|
+
patchState(store, {
|
|
902
|
+
expandedWeekIndex: current === weekIndex ? null : weekIndex
|
|
903
|
+
});
|
|
904
|
+
},
|
|
905
|
+
// --- Drag & Drop Methods ---
|
|
906
|
+
setDragStart(eventId, grabDate) {
|
|
907
|
+
patchState(store, {
|
|
908
|
+
dragState: {
|
|
909
|
+
eventId,
|
|
910
|
+
grabDate,
|
|
911
|
+
hoverDate: null
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
},
|
|
915
|
+
setDragHover(date) {
|
|
916
|
+
patchState(store, (state) => ({
|
|
917
|
+
dragState: {
|
|
918
|
+
...state.dragState,
|
|
919
|
+
hoverDate: date
|
|
920
|
+
}
|
|
921
|
+
}));
|
|
922
|
+
},
|
|
923
|
+
clearDragState() {
|
|
924
|
+
patchState(store, {
|
|
925
|
+
dragState: {
|
|
926
|
+
eventId: null,
|
|
927
|
+
grabDate: null,
|
|
928
|
+
hoverDate: null
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
},
|
|
932
|
+
/**
|
|
933
|
+
* Updates the position of the currently dragged event based on the hover date.
|
|
934
|
+
* Calculates the offset between the original grab date and the new hover date.
|
|
935
|
+
*/
|
|
936
|
+
updateDraggedEventPosition() {
|
|
937
|
+
const { eventId, grabDate, hoverDate } = store.dragState();
|
|
938
|
+
if (!eventId || !grabDate || !hoverDate)
|
|
939
|
+
return;
|
|
940
|
+
const event = store.events().get(eventId);
|
|
941
|
+
if (!event)
|
|
942
|
+
return;
|
|
943
|
+
const offset = differenceInCalendarDays(hoverDate, grabDate);
|
|
944
|
+
if (offset === 0)
|
|
945
|
+
return;
|
|
946
|
+
if (event.type === 'all-day') {
|
|
947
|
+
const partial = {
|
|
948
|
+
date: addDays(event.date, offset)
|
|
949
|
+
};
|
|
950
|
+
if (event.endDate) {
|
|
951
|
+
partial.endDate = addDays(event.endDate, offset);
|
|
952
|
+
}
|
|
953
|
+
this.updateEvent(eventId, partial);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
const partial = {
|
|
957
|
+
start: addDays(event.start, offset),
|
|
958
|
+
end: addDays(event.end, offset)
|
|
959
|
+
};
|
|
960
|
+
this.updateEvent(eventId, partial);
|
|
961
|
+
}
|
|
962
|
+
// Update the grab date to the new hover date to maintain the relative "grabbed" point
|
|
963
|
+
patchState(store, (state) => ({
|
|
964
|
+
dragState: {
|
|
965
|
+
...state.dragState,
|
|
966
|
+
grabDate: hoverDate
|
|
967
|
+
}
|
|
968
|
+
}));
|
|
969
|
+
},
|
|
970
|
+
// --- Resizing Methods ---
|
|
971
|
+
setResizeStart(eventId, side) {
|
|
972
|
+
patchState(store, {
|
|
973
|
+
resizeState: {
|
|
974
|
+
eventId,
|
|
975
|
+
side,
|
|
976
|
+
hoverDate: null
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
},
|
|
980
|
+
setResizeHover(date) {
|
|
981
|
+
patchState(store, (state) => ({
|
|
982
|
+
resizeState: {
|
|
983
|
+
...state.resizeState,
|
|
984
|
+
hoverDate: date
|
|
985
|
+
}
|
|
986
|
+
}));
|
|
987
|
+
},
|
|
988
|
+
clearResizeState() {
|
|
989
|
+
patchState(store, {
|
|
990
|
+
resizeState: {
|
|
991
|
+
eventId: null,
|
|
992
|
+
side: null,
|
|
993
|
+
hoverDate: null
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
},
|
|
997
|
+
/**
|
|
998
|
+
* Updates the start or end date of the currently resized event based on the hover date.
|
|
999
|
+
*/
|
|
1000
|
+
updateResizedEvent() {
|
|
1001
|
+
const { eventId, side, hoverDate } = store.resizeState();
|
|
1002
|
+
if (!eventId || !side || !hoverDate)
|
|
1003
|
+
return;
|
|
1004
|
+
const event = store.events().get(eventId);
|
|
1005
|
+
if (!event)
|
|
1006
|
+
return;
|
|
1007
|
+
const normalizedHover = startOfDay(hoverDate);
|
|
1008
|
+
if (event.type === 'all-day') {
|
|
1009
|
+
const partial = {};
|
|
1010
|
+
const eventDate = startOfDay(event.date);
|
|
1011
|
+
const currentEnd = event.endDate ? startOfDay(event.endDate) : eventDate;
|
|
1012
|
+
if (side === 'left') {
|
|
1013
|
+
// Cannot exceed the current end date
|
|
1014
|
+
partial.date = normalizedHover > currentEnd ? currentEnd : normalizedHover;
|
|
1015
|
+
}
|
|
1016
|
+
else if (side === 'right') {
|
|
1017
|
+
// Cannot be before the current start date
|
|
1018
|
+
partial.endDate = normalizedHover < eventDate ? eventDate : normalizedHover;
|
|
1019
|
+
}
|
|
1020
|
+
this.updateEvent(eventId, partial);
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
const partial = {};
|
|
1024
|
+
if (side === 'left') {
|
|
1025
|
+
// Cannot exceed end time
|
|
1026
|
+
partial.start = hoverDate > event.end ? event.end : hoverDate;
|
|
1027
|
+
}
|
|
1028
|
+
else if (side === 'right') {
|
|
1029
|
+
// Cannot be before start time
|
|
1030
|
+
partial.end = hoverDate < event.start ? event.start : hoverDate;
|
|
1031
|
+
}
|
|
1032
|
+
this.updateEvent(eventId, partial);
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
// --- Interaction Mutex ---
|
|
1036
|
+
setInteractionMode(mode) {
|
|
1037
|
+
patchState(store, { interactionMode: mode });
|
|
1038
|
+
},
|
|
1039
|
+
// --- Hover State Methods ---
|
|
1040
|
+
setHoveredEvent(eventId) {
|
|
1041
|
+
patchState(store, { hoveredEventId: eventId });
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}));
|
|
1045
|
+
|
|
1046
|
+
class Selectable {
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Directive that enables mouse-based selection on calendar views.
|
|
1051
|
+
*
|
|
1052
|
+
* Works with components that implement the Selectable interface to translate
|
|
1053
|
+
* visual coordinates into dates/resources. Handles all mouse interaction logic
|
|
1054
|
+
* and emits selection events.
|
|
1055
|
+
*
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```html
|
|
1058
|
+
* <div [mglonSelectable]="this"
|
|
1059
|
+
* (selectionStart)="onStart($event)"
|
|
1060
|
+
* (selectionChange)="onChange($event)"
|
|
1061
|
+
* (selectionEnd)="onEnd($event)">
|
|
1062
|
+
* </div>
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
class SelectableDirective {
|
|
1066
|
+
elementRef = inject(ElementRef);
|
|
1067
|
+
platformId = inject(PLATFORM_ID);
|
|
1068
|
+
store = inject(CalendarStore);
|
|
1069
|
+
/**
|
|
1070
|
+
* The component that implements Selectable interface.
|
|
1071
|
+
* Provides the getDateFromPoint() method to translate coordinates to dates.
|
|
1072
|
+
*/
|
|
1073
|
+
selectable = null;
|
|
1074
|
+
/**
|
|
1075
|
+
* Emitted when user starts a selection (mousedown)
|
|
1076
|
+
*/
|
|
1077
|
+
selectionStart = output();
|
|
1078
|
+
/**
|
|
1079
|
+
* Emitted when selection changes (mousemove while selecting)
|
|
1080
|
+
*/
|
|
1081
|
+
selectionChange = output();
|
|
1082
|
+
/**
|
|
1083
|
+
* Emitted when selection ends (mouseup)
|
|
1084
|
+
*/
|
|
1085
|
+
selectionEnd = output();
|
|
1086
|
+
/**
|
|
1087
|
+
* Current selection rectangle coordinates (relative to container)
|
|
1088
|
+
*/
|
|
1089
|
+
selection = signal(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
1090
|
+
/**
|
|
1091
|
+
* Whether user is currently selecting (mouse button is down)
|
|
1092
|
+
*/
|
|
1093
|
+
isSelecting = signal(false, ...(ngDevMode ? [{ debugName: "isSelecting" }] : []));
|
|
1094
|
+
/** Starting point of the selection */
|
|
1095
|
+
startPoint = null;
|
|
1096
|
+
/** Date/resource data at the starting point */
|
|
1097
|
+
startData = null;
|
|
1098
|
+
/** Date/resource data at the current cursor position */
|
|
1099
|
+
currentData = null;
|
|
1100
|
+
/**
|
|
1101
|
+
* Handles mousedown event to initiate selection
|
|
1102
|
+
*/
|
|
1103
|
+
onMouseDown(event) {
|
|
1104
|
+
if (!this.canSelect() || this.store.interactionMode() !== 'none')
|
|
1105
|
+
return;
|
|
1106
|
+
this.store.setInteractionMode('selecting');
|
|
1107
|
+
const coords = this.getRelativeCoordinates(event);
|
|
1108
|
+
this.startSelection(coords, event.clientX, event.clientY);
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Handles mousemove event to update selection
|
|
1112
|
+
*/
|
|
1113
|
+
onMouseMove(event) {
|
|
1114
|
+
if (!this.isSelecting() || !this.startPoint || !this.canSelect())
|
|
1115
|
+
return;
|
|
1116
|
+
const coords = this.getRelativeCoordinates(event);
|
|
1117
|
+
this.updateSelection(coords, event.clientX, event.clientY);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Handles mouseup event to complete selection
|
|
1121
|
+
*/
|
|
1122
|
+
onMouseUp(event) {
|
|
1123
|
+
if (!this.isSelecting() || !this.canSelect())
|
|
1124
|
+
return;
|
|
1125
|
+
this.store.setInteractionMode('none');
|
|
1126
|
+
this.endSelection();
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Checks if selection is possible (browser platform and selectable is set)
|
|
1130
|
+
*/
|
|
1131
|
+
canSelect() {
|
|
1132
|
+
return isPlatformBrowser(this.platformId) && !!this.selectable;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Converts mouse event coordinates to coordinates relative to the container
|
|
1136
|
+
* Takes into account scroll position for scrollable containers.
|
|
1137
|
+
*
|
|
1138
|
+
* @param event - Mouse event with clientX/clientY
|
|
1139
|
+
* @returns Coordinates relative to the container's top-left corner (including scroll)
|
|
1140
|
+
*/
|
|
1141
|
+
getRelativeCoordinates(event) {
|
|
1142
|
+
const containerElement = this.elementRef.nativeElement;
|
|
1143
|
+
const rect = containerElement.getBoundingClientRect();
|
|
1144
|
+
return {
|
|
1145
|
+
x: event.clientX - rect.left + containerElement.scrollLeft,
|
|
1146
|
+
y: event.clientY - rect.top + containerElement.scrollTop
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Initiates a new selection
|
|
1151
|
+
*
|
|
1152
|
+
* @param coords - Relative coordinates where selection started
|
|
1153
|
+
* @param absoluteX - Absolute X coordinate (for element detection)
|
|
1154
|
+
* @param absoluteY - Absolute Y coordinate (for element detection)
|
|
1155
|
+
*/
|
|
1156
|
+
startSelection(coords, absoluteX, absoluteY) {
|
|
1157
|
+
this.isSelecting.set(true);
|
|
1158
|
+
this.startPoint = coords;
|
|
1159
|
+
// Get the date/resource from the starting point
|
|
1160
|
+
if (!this.selectable)
|
|
1161
|
+
return;
|
|
1162
|
+
this.startData = this.selectable.getDateFromPoint(coords.x, coords.y);
|
|
1163
|
+
this.currentData = this.startData;
|
|
1164
|
+
// Initialize selection rectangle at a single point
|
|
1165
|
+
this.selection.set({
|
|
1166
|
+
top: coords.y,
|
|
1167
|
+
left: coords.x,
|
|
1168
|
+
bottom: coords.y,
|
|
1169
|
+
right: coords.x
|
|
1170
|
+
});
|
|
1171
|
+
// Emit selection start event
|
|
1172
|
+
this.selectionStart.emit({
|
|
1173
|
+
start: this.startData,
|
|
1174
|
+
end: this.currentData
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Updates an ongoing selection
|
|
1179
|
+
*
|
|
1180
|
+
* @param coords - Current relative coordinates
|
|
1181
|
+
* @param absoluteX - Absolute X coordinate (for element detection)
|
|
1182
|
+
* @param absoluteY - Absolute Y coordinate (for element detection)
|
|
1183
|
+
*/
|
|
1184
|
+
updateSelection(coords, absoluteX, absoluteY) {
|
|
1185
|
+
if (!this.startPoint)
|
|
1186
|
+
return;
|
|
1187
|
+
// Calculate selection rectangle (supports dragging in any direction)
|
|
1188
|
+
const rect = this.calculateSelectionRectangle(this.startPoint, coords);
|
|
1189
|
+
this.selection.set(rect);
|
|
1190
|
+
// Get the date/resource from the current point
|
|
1191
|
+
if (!this.selectable)
|
|
1192
|
+
return;
|
|
1193
|
+
this.currentData = this.selectable.getDateFromPoint(coords.x, coords.y);
|
|
1194
|
+
// Emit selection change event
|
|
1195
|
+
this.selectionChange.emit({
|
|
1196
|
+
start: this.startData,
|
|
1197
|
+
end: this.currentData
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Completes the current selection
|
|
1202
|
+
*/
|
|
1203
|
+
endSelection() {
|
|
1204
|
+
this.isSelecting.set(false);
|
|
1205
|
+
// Emit final selection event
|
|
1206
|
+
this.selectionEnd.emit({
|
|
1207
|
+
start: this.startData,
|
|
1208
|
+
end: this.currentData
|
|
1209
|
+
});
|
|
1210
|
+
// Clear selection state
|
|
1211
|
+
this.clearSelection();
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Calculates the selection rectangle from start and end points.
|
|
1215
|
+
* Handles dragging in any direction by using min/max.
|
|
1216
|
+
*
|
|
1217
|
+
* @param start - Starting point coordinates
|
|
1218
|
+
* @param end - Ending point coordinates
|
|
1219
|
+
* @returns Selection rectangle with top, left, bottom, right
|
|
1220
|
+
*/
|
|
1221
|
+
calculateSelectionRectangle(start, end) {
|
|
1222
|
+
return {
|
|
1223
|
+
top: Math.min(start.y, end.y),
|
|
1224
|
+
left: Math.min(start.x, end.x),
|
|
1225
|
+
bottom: Math.max(start.y, end.y),
|
|
1226
|
+
right: Math.max(start.x, end.x)
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Clears all selection state
|
|
1231
|
+
*/
|
|
1232
|
+
clearSelection() {
|
|
1233
|
+
this.selection.set(null);
|
|
1234
|
+
this.startPoint = null;
|
|
1235
|
+
this.startData = null;
|
|
1236
|
+
this.currentData = null;
|
|
1237
|
+
}
|
|
1238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SelectableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1239
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: SelectableDirective, isStandalone: true, selector: "[mglonSelectable]", inputs: { selectable: ["mglonSelectable", "selectable"] }, outputs: { selectionStart: "selectionStart", selectionChange: "selectionChange", selectionEnd: "selectionEnd" }, host: { listeners: { "mousedown": "onMouseDown($event)", "mousemove": "onMouseMove($event)", "mouseup": "onMouseUp($event)" } }, ngImport: i0 });
|
|
1240
|
+
}
|
|
1241
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SelectableDirective, decorators: [{
|
|
1242
|
+
type: Directive,
|
|
1243
|
+
args: [{
|
|
1244
|
+
selector: '[mglonSelectable]',
|
|
1245
|
+
standalone: true
|
|
1246
|
+
}]
|
|
1247
|
+
}], propDecorators: { selectable: [{
|
|
1248
|
+
type: Input,
|
|
1249
|
+
args: ['mglonSelectable']
|
|
1250
|
+
}], selectionStart: [{ type: i0.Output, args: ["selectionStart"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionEnd: [{ type: i0.Output, args: ["selectionEnd"] }], onMouseDown: [{
|
|
1251
|
+
type: HostListener,
|
|
1252
|
+
args: ['mousedown', ['$event']]
|
|
1253
|
+
}], onMouseMove: [{
|
|
1254
|
+
type: HostListener,
|
|
1255
|
+
args: ['mousemove', ['$event']]
|
|
1256
|
+
}], onMouseUp: [{
|
|
1257
|
+
type: HostListener,
|
|
1258
|
+
args: ['mouseup', ['$event']]
|
|
1259
|
+
}] } });
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Week grid component displaying time slots for 7 days.
|
|
1263
|
+
*
|
|
1264
|
+
* Implements the Selectable interface to enable time-based selection.
|
|
1265
|
+
* Displays a scrollable grid of 30-minute time slots (48 per day).
|
|
1266
|
+
*
|
|
1267
|
+
* Features:
|
|
1268
|
+
* - 7 columns (days) × 48 rows (time slots)
|
|
1269
|
+
* - Scrollable container
|
|
1270
|
+
* - Mouse-based time selection
|
|
1271
|
+
* - 50px minimum height per slot
|
|
1272
|
+
*
|
|
1273
|
+
* @example
|
|
1274
|
+
* ```html
|
|
1275
|
+
* <mglon-week-grid
|
|
1276
|
+
* [currentDate]="selectedDate"
|
|
1277
|
+
* (selectionEnd)="onTimeRangeSelected($event)">
|
|
1278
|
+
* </mglon-week-grid>
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
class WeekGrid {
|
|
1282
|
+
elementRef = inject(ElementRef);
|
|
1283
|
+
store = inject(CalendarStore);
|
|
1284
|
+
backgroundSelection = computed(() => this.store.config().backgroundSelection ?? true, ...(ngDevMode ? [{ debugName: "backgroundSelection" }] : []));
|
|
1285
|
+
showNowIndicator = computed(() => this.store.config().showNowIndicator ?? true, ...(ngDevMode ? [{ debugName: "showNowIndicator" }] : []));
|
|
1286
|
+
/**
|
|
1287
|
+
* Reference to the SelectableDirective instance
|
|
1288
|
+
*/
|
|
1289
|
+
selectableDirective = viewChild(SelectableDirective, ...(ngDevMode ? [{ debugName: "selectableDirective" }] : []));
|
|
1290
|
+
/**
|
|
1291
|
+
* The date to display the week for
|
|
1292
|
+
*/
|
|
1293
|
+
currentDate = input(new Date(), ...(ngDevMode ? [{ debugName: "currentDate" }] : []));
|
|
1294
|
+
/**
|
|
1295
|
+
* Selection events
|
|
1296
|
+
*/
|
|
1297
|
+
selectionStart = output();
|
|
1298
|
+
selectionChange = output();
|
|
1299
|
+
selectionEnd = output();
|
|
1300
|
+
/**
|
|
1301
|
+
* Computed 2D array of time slots [days][slots]
|
|
1302
|
+
*/
|
|
1303
|
+
timeSlots = computed(() => {
|
|
1304
|
+
return getWeekTimeSlots(this.currentDate());
|
|
1305
|
+
}, ...(ngDevMode ? [{ debugName: "timeSlots" }] : []));
|
|
1306
|
+
/**
|
|
1307
|
+
* Computed array of week days for column headers
|
|
1308
|
+
*/
|
|
1309
|
+
weekDays = computed(() => {
|
|
1310
|
+
return getWeekDays(this.currentDate());
|
|
1311
|
+
}, ...(ngDevMode ? [{ debugName: "weekDays" }] : []));
|
|
1312
|
+
/**
|
|
1313
|
+
* Computed index of today's column (-1 if today is not in this week)
|
|
1314
|
+
*/
|
|
1315
|
+
todayColumnIndex = computed(() => {
|
|
1316
|
+
const days = this.weekDays();
|
|
1317
|
+
return days.findIndex(day => day.isToday);
|
|
1318
|
+
}, ...(ngDevMode ? [{ debugName: "todayColumnIndex" }] : []));
|
|
1319
|
+
/**
|
|
1320
|
+
* Slot height in pixels (matches CSS min-height)
|
|
1321
|
+
*/
|
|
1322
|
+
SLOT_HEIGHT = 50;
|
|
1323
|
+
/**
|
|
1324
|
+
* Time column width in pixels (matches CSS grid-template-columns)
|
|
1325
|
+
*/
|
|
1326
|
+
TIME_COLUMN_WIDTH = 60;
|
|
1327
|
+
/**
|
|
1328
|
+
* Calculates the left position for the today indicator line
|
|
1329
|
+
* @returns Position in pixels from the left edge
|
|
1330
|
+
*/
|
|
1331
|
+
getTodayIndicatorPosition() {
|
|
1332
|
+
const index = this.todayColumnIndex();
|
|
1333
|
+
if (index === -1)
|
|
1334
|
+
return 0;
|
|
1335
|
+
const gridElement = this.elementRef.nativeElement;
|
|
1336
|
+
if (!gridElement)
|
|
1337
|
+
return 0;
|
|
1338
|
+
const gridRect = gridElement.getBoundingClientRect();
|
|
1339
|
+
const dayColumnWidth = (gridRect.width - this.TIME_COLUMN_WIDTH - 8) / 7; // -8 for scrollbar
|
|
1340
|
+
// Position at the center of the column
|
|
1341
|
+
return this.TIME_COLUMN_WIDTH + (dayColumnWidth * index) + (dayColumnWidth / 2);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Implements Selectable.getDateFromPoint()
|
|
1345
|
+
*
|
|
1346
|
+
* Translates visual coordinates into a specific date and time.
|
|
1347
|
+
*
|
|
1348
|
+
* Algorithm:
|
|
1349
|
+
* 1. Convert relative coordinates to absolute
|
|
1350
|
+
* 2. Determine which day column (0-6)
|
|
1351
|
+
* 3. Determine which time slot row (0-47)
|
|
1352
|
+
* 4. Calculate exact time (hour + minute)
|
|
1353
|
+
* 5. Return Date object with specific date and time
|
|
1354
|
+
*
|
|
1355
|
+
* @param x - X coordinate relative to the grid container
|
|
1356
|
+
* @param y - Y coordinate relative to the grid container
|
|
1357
|
+
* @returns Object with date property (including time), or null if invalid
|
|
1358
|
+
*/
|
|
1359
|
+
getDateFromPoint(x, y) {
|
|
1360
|
+
const gridElement = this.elementRef.nativeElement;
|
|
1361
|
+
if (!gridElement)
|
|
1362
|
+
return null;
|
|
1363
|
+
// Account for time column
|
|
1364
|
+
const adjustedX = x - this.TIME_COLUMN_WIDTH;
|
|
1365
|
+
if (adjustedX < 0)
|
|
1366
|
+
return null; // Clicked on time column
|
|
1367
|
+
// Calculate which day column (0-6)
|
|
1368
|
+
const gridRect = gridElement.getBoundingClientRect();
|
|
1369
|
+
const dayColumnWidth = (gridRect.width - this.TIME_COLUMN_WIDTH) / 7;
|
|
1370
|
+
const dayIndex = Math.floor(adjustedX / dayColumnWidth);
|
|
1371
|
+
if (dayIndex < 0 || dayIndex > 6)
|
|
1372
|
+
return null;
|
|
1373
|
+
// Calculate which time slot (0-47)
|
|
1374
|
+
const slotIndex = Math.floor(y / this.SLOT_HEIGHT);
|
|
1375
|
+
if (slotIndex < 0 || slotIndex > 47)
|
|
1376
|
+
return null;
|
|
1377
|
+
// Get the time slot
|
|
1378
|
+
const timeSlots = this.timeSlots();
|
|
1379
|
+
if (!timeSlots[dayIndex] || !timeSlots[dayIndex][slotIndex])
|
|
1380
|
+
return null;
|
|
1381
|
+
return { date: timeSlots[dayIndex][slotIndex].date };
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Handles selection start event
|
|
1385
|
+
*/
|
|
1386
|
+
onSelectionStart(result) {
|
|
1387
|
+
this.selectionStart.emit(result);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Handles selection change event
|
|
1391
|
+
*/
|
|
1392
|
+
onSelectionChange(result) {
|
|
1393
|
+
this.selectionChange.emit(result);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Handles selection end event
|
|
1397
|
+
*/
|
|
1398
|
+
onSelectionEnd(result) {
|
|
1399
|
+
this.selectionEnd.emit(result);
|
|
1400
|
+
}
|
|
1401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1402
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: WeekGrid, isStandalone: true, selector: "mglon-week-grid", inputs: { currentDate: { classPropertyName: "currentDate", publicName: "currentDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionStart: "selectionStart", selectionChange: "selectionChange", selectionEnd: "selectionEnd" }, viewQueries: [{ propertyName: "selectableDirective", first: true, predicate: SelectableDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mglon-week-grid\" [mglonSelectable]=\"backgroundSelection() ? this : null\"\n (selectionStart)=\"onSelectionStart($event)\" (selectionChange)=\"onSelectionChange($event)\"\n (selectionEnd)=\"onSelectionEnd($event)\">\n\n <!-- Time column -->\n <div class=\"mglon-week-grid__time-column\">\n @for (slot of timeSlots()[0]; track slot.date.getTime()) {\n <mglon-time-slot [slot]=\"slot\" [showTimeLabel]=\"true\"></mglon-time-slot>\n }\n </div>\n\n <!-- Day columns -->\n @for (daySlots of timeSlots(); track $index) {\n <div class=\"mglon-week-grid__day-column\">\n @for (slot of daySlots; track slot.date.getTime()) {\n <mglon-time-slot [slot]=\"slot\"></mglon-time-slot>\n }\n </div>\n }\n\n <!-- Selection overlay -->\n @if (selectableDirective() && backgroundSelection()) {\n <mglon-selection [selection]=\"selectableDirective()!.selection()\"\n [isSelecting]=\"selectableDirective()!.isSelecting()\">\n </mglon-selection>\n }\n\n <!-- Today indicator line -->\n @if (todayColumnIndex() !== -1 && showNowIndicator()) {\n <div class=\"mglon-week-grid__today-indicator\" [style.left.px]=\"getTodayIndicatorPosition()\">\n </div>\n }\n\n <!-- Events Overlay -->\n <div class=\"mglon-week-grid__events-overlay\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".mglon-week-grid{--week-grid-time-col-width: var(--mglon-week-grid-time-column-width, 60px);--week-grid-height: var(--mglon-week-grid-height, calc(100vh - 200px) );--week-grid-padding-top: var(--mglon-week-grid-padding-top, 12px);--week-grid-scrollbar-width: var(--mglon-week-grid-scrollbar-width, 8px);--week-grid-scrollbar-track-bg: var(--mglon-week-grid-scrollbar-track-bg, var(--mglon-schedule-surface-variant));--week-grid-scrollbar-thumb-bg: var(--mglon-week-grid-scrollbar-thumb-bg, var(--mglon-schedule-border-color));--week-grid-scrollbar-thumb-hover-bg: var(--mglon-week-grid-scrollbar-thumb-hover-bg, var(--mglon-schedule-on-surface-variant));--week-grid-time-col-bg: var(--mglon-week-grid-time-column-bg, var(--mglon-schedule-surface-variant));display:grid;grid-template-columns:var(--week-grid-time-col-width) repeat(7,1fr);position:relative;overflow-y:auto;overflow-x:hidden;max-height:var(--week-grid-height);-webkit-user-select:none;user-select:none;cursor:crosshair;padding-top:var(--week-grid-padding-top)}.mglon-week-grid::-webkit-scrollbar{width:var(--week-grid-scrollbar-width)}.mglon-week-grid::-webkit-scrollbar-track{background:var(--week-grid-scrollbar-track-bg)}.mglon-week-grid::-webkit-scrollbar-thumb{background:var(--week-grid-scrollbar-thumb-bg);border-radius:4px}.mglon-week-grid::-webkit-scrollbar-thumb:hover{background:var(--week-grid-scrollbar-thumb-hover-bg)}.mglon-week-grid__time-column{display:flex;flex-direction:column;border-right:1px solid var(--mglon-schedule-border);background:var(--week-grid-time-col-bg)}.mglon-week-grid__day-column{display:flex;flex-direction:column;border-right:1px solid var(--mglon-schedule-border)}.mglon-week-grid__day-column:last-child{border-right:none}.mglon-week-grid__today-indicator{--mglon-week-grid-today-color: var(--mglon-schedule-error);position:absolute;top:-60px;height:2460px;width:2px;background:var(--mglon-week-grid-today-color);pointer-events:none;z-index:5;transform:translate(-1px)}.mglon-week-grid__events-overlay{position:absolute;top:var(--week-grid-padding-top);left:var(--week-grid-time-col-width);right:0;bottom:0;pointer-events:none;transform:translate(-2px)}.mglon-week-grid__events-overlay>*{pointer-events:auto}\n"], dependencies: [{ kind: "component", type: TimeSlotComponent, selector: "mglon-time-slot", inputs: ["slot", "showTimeLabel"] }, { kind: "component", type: Selection, selector: "mglon-selection", inputs: ["selection", "isSelecting"] }, { kind: "directive", type: SelectableDirective, selector: "[mglonSelectable]", inputs: ["mglonSelectable"], outputs: ["selectionStart", "selectionChange", "selectionEnd"] }] });
|
|
1403
|
+
}
|
|
1404
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekGrid, decorators: [{
|
|
1405
|
+
type: Component,
|
|
1406
|
+
args: [{ selector: 'mglon-week-grid', standalone: true, imports: [TimeSlotComponent, Selection, SelectableDirective], template: "<div class=\"mglon-week-grid\" [mglonSelectable]=\"backgroundSelection() ? this : null\"\n (selectionStart)=\"onSelectionStart($event)\" (selectionChange)=\"onSelectionChange($event)\"\n (selectionEnd)=\"onSelectionEnd($event)\">\n\n <!-- Time column -->\n <div class=\"mglon-week-grid__time-column\">\n @for (slot of timeSlots()[0]; track slot.date.getTime()) {\n <mglon-time-slot [slot]=\"slot\" [showTimeLabel]=\"true\"></mglon-time-slot>\n }\n </div>\n\n <!-- Day columns -->\n @for (daySlots of timeSlots(); track $index) {\n <div class=\"mglon-week-grid__day-column\">\n @for (slot of daySlots; track slot.date.getTime()) {\n <mglon-time-slot [slot]=\"slot\"></mglon-time-slot>\n }\n </div>\n }\n\n <!-- Selection overlay -->\n @if (selectableDirective() && backgroundSelection()) {\n <mglon-selection [selection]=\"selectableDirective()!.selection()\"\n [isSelecting]=\"selectableDirective()!.isSelecting()\">\n </mglon-selection>\n }\n\n <!-- Today indicator line -->\n @if (todayColumnIndex() !== -1 && showNowIndicator()) {\n <div class=\"mglon-week-grid__today-indicator\" [style.left.px]=\"getTodayIndicatorPosition()\">\n </div>\n }\n\n <!-- Events Overlay -->\n <div class=\"mglon-week-grid__events-overlay\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".mglon-week-grid{--week-grid-time-col-width: var(--mglon-week-grid-time-column-width, 60px);--week-grid-height: var(--mglon-week-grid-height, calc(100vh - 200px) );--week-grid-padding-top: var(--mglon-week-grid-padding-top, 12px);--week-grid-scrollbar-width: var(--mglon-week-grid-scrollbar-width, 8px);--week-grid-scrollbar-track-bg: var(--mglon-week-grid-scrollbar-track-bg, var(--mglon-schedule-surface-variant));--week-grid-scrollbar-thumb-bg: var(--mglon-week-grid-scrollbar-thumb-bg, var(--mglon-schedule-border-color));--week-grid-scrollbar-thumb-hover-bg: var(--mglon-week-grid-scrollbar-thumb-hover-bg, var(--mglon-schedule-on-surface-variant));--week-grid-time-col-bg: var(--mglon-week-grid-time-column-bg, var(--mglon-schedule-surface-variant));display:grid;grid-template-columns:var(--week-grid-time-col-width) repeat(7,1fr);position:relative;overflow-y:auto;overflow-x:hidden;max-height:var(--week-grid-height);-webkit-user-select:none;user-select:none;cursor:crosshair;padding-top:var(--week-grid-padding-top)}.mglon-week-grid::-webkit-scrollbar{width:var(--week-grid-scrollbar-width)}.mglon-week-grid::-webkit-scrollbar-track{background:var(--week-grid-scrollbar-track-bg)}.mglon-week-grid::-webkit-scrollbar-thumb{background:var(--week-grid-scrollbar-thumb-bg);border-radius:4px}.mglon-week-grid::-webkit-scrollbar-thumb:hover{background:var(--week-grid-scrollbar-thumb-hover-bg)}.mglon-week-grid__time-column{display:flex;flex-direction:column;border-right:1px solid var(--mglon-schedule-border);background:var(--week-grid-time-col-bg)}.mglon-week-grid__day-column{display:flex;flex-direction:column;border-right:1px solid var(--mglon-schedule-border)}.mglon-week-grid__day-column:last-child{border-right:none}.mglon-week-grid__today-indicator{--mglon-week-grid-today-color: var(--mglon-schedule-error);position:absolute;top:-60px;height:2460px;width:2px;background:var(--mglon-week-grid-today-color);pointer-events:none;z-index:5;transform:translate(-1px)}.mglon-week-grid__events-overlay{position:absolute;top:var(--week-grid-padding-top);left:var(--week-grid-time-col-width);right:0;bottom:0;pointer-events:none;transform:translate(-2px)}.mglon-week-grid__events-overlay>*{pointer-events:auto}\n"] }]
|
|
1407
|
+
}], propDecorators: { selectableDirective: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SelectableDirective), { isSignal: true }] }], currentDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentDate", required: false }] }], selectionStart: [{ type: i0.Output, args: ["selectionStart"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionEnd: [{ type: i0.Output, args: ["selectionEnd"] }] } });
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Week view container component.
|
|
1411
|
+
*/
|
|
1412
|
+
class WeekView {
|
|
1413
|
+
store = inject(CalendarStore);
|
|
1414
|
+
// ============================================
|
|
1415
|
+
// OUTPUT EVENTS
|
|
1416
|
+
// ============================================
|
|
1417
|
+
/** Emitted when selection starts */
|
|
1418
|
+
selectionStart = output();
|
|
1419
|
+
/** Emitted while selecting */
|
|
1420
|
+
selectionChange = output();
|
|
1421
|
+
/** Emitted when selection ends */
|
|
1422
|
+
selectionEnd = output();
|
|
1423
|
+
// Get week grid element reference
|
|
1424
|
+
gridElement = viewChild(WeekGrid, { ...(ngDevMode ? { debugName: "gridElement" } : {}), read: ElementRef });
|
|
1425
|
+
animationState = computed(() => {
|
|
1426
|
+
const date = this.store.currentDate();
|
|
1427
|
+
return this.getWeekNumber(date);
|
|
1428
|
+
}, ...(ngDevMode ? [{ debugName: "animationState" }] : []));
|
|
1429
|
+
constructor() {
|
|
1430
|
+
afterNextRender(() => {
|
|
1431
|
+
// Logic for grid sizing can remain if needed for the grid itself,
|
|
1432
|
+
// but let's see if we can simplify it.
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
getWeekNumber(date) {
|
|
1436
|
+
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
|
|
1437
|
+
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
|
|
1438
|
+
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
|
|
1439
|
+
}
|
|
1440
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1441
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.6", type: WeekView, isStandalone: true, selector: "mglon-week-view", outputs: { selectionStart: "selectionStart", selectionChange: "selectionChange", selectionEnd: "selectionEnd" }, viewQueries: [{ propertyName: "gridElement", first: true, predicate: WeekGrid, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div class=\"mglon-week-view\" [@weekTransition]=\"animationState()\">\n <mglon-week-header [currentDate]=\"store.currentDate()\"></mglon-week-header>\n <mglon-week-grid [currentDate]=\"store.currentDate()\" (selectionStart)=\"selectionStart.emit($event)\"\n (selectionChange)=\"selectionChange.emit($event)\" (selectionEnd)=\"selectionEnd.emit($event)\">\n </mglon-week-grid>\n</div>", styles: [".mglon-week-view{--week-view-bg: var(--mglon-week-view-bg, var(--mglon-schedule-surface));display:flex;flex-direction:column;height:100%;background:var(--week-view-bg)}\n"], dependencies: [{ kind: "component", type: WeekHeader, selector: "mglon-week-header", inputs: ["currentDate"] }, { kind: "component", type: WeekGrid, selector: "mglon-week-grid", inputs: ["currentDate"], outputs: ["selectionStart", "selectionChange", "selectionEnd"] }], animations: [
|
|
1442
|
+
trigger('weekTransition', [
|
|
1443
|
+
transition('* => *', [
|
|
1444
|
+
style({ opacity: 0, transform: 'translateX({{ direction }}%)' }),
|
|
1445
|
+
animate('500ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
|
|
1446
|
+
], { params: { direction: 0 } })
|
|
1447
|
+
])
|
|
1448
|
+
] });
|
|
1449
|
+
}
|
|
1450
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WeekView, decorators: [{
|
|
1451
|
+
type: Component,
|
|
1452
|
+
args: [{ selector: 'mglon-week-view', standalone: true, imports: [WeekHeader, WeekGrid], animations: [
|
|
1453
|
+
trigger('weekTransition', [
|
|
1454
|
+
transition('* => *', [
|
|
1455
|
+
style({ opacity: 0, transform: 'translateX({{ direction }}%)' }),
|
|
1456
|
+
animate('500ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
|
|
1457
|
+
], { params: { direction: 0 } })
|
|
1458
|
+
])
|
|
1459
|
+
], template: "<div class=\"mglon-week-view\" [@weekTransition]=\"animationState()\">\n <mglon-week-header [currentDate]=\"store.currentDate()\"></mglon-week-header>\n <mglon-week-grid [currentDate]=\"store.currentDate()\" (selectionStart)=\"selectionStart.emit($event)\"\n (selectionChange)=\"selectionChange.emit($event)\" (selectionEnd)=\"selectionEnd.emit($event)\">\n </mglon-week-grid>\n</div>", styles: [".mglon-week-view{--week-view-bg: var(--mglon-week-view-bg, var(--mglon-schedule-surface));display:flex;flex-direction:column;height:100%;background:var(--week-view-bg)}\n"] }]
|
|
1460
|
+
}], ctorParameters: () => [], propDecorators: { selectionStart: [{ type: i0.Output, args: ["selectionStart"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionEnd: [{ type: i0.Output, args: ["selectionEnd"] }], gridElement: [{ type: i0.ViewChild, args: [i0.forwardRef(() => WeekGrid), { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
1461
|
+
|
|
1462
|
+
const ICONS = {
|
|
1463
|
+
'chevron-left': `
|
|
1464
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1465
|
+
<path fill-rule="evenodd" d="M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z" clip-rule="evenodd" />
|
|
1466
|
+
</svg>`,
|
|
1467
|
+
'chevron-right': `
|
|
1468
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1469
|
+
<path fill-rule="evenodd" d="M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z" clip-rule="evenodd" />
|
|
1470
|
+
</svg>`,
|
|
1471
|
+
'chevron-down': `
|
|
1472
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1473
|
+
<path fill-rule="evenodd" d="M12.53 16.28a.75.75 0 01-1.06 0l-7.5-7.5a.75.75 0 011.06-1.06L12 14.69l6.97-6.97a.75.75 0 111.06 1.06l-7.5 7.5z" clip-rule="evenodd" />
|
|
1474
|
+
</svg>`,
|
|
1475
|
+
'chevron-up': `
|
|
1476
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1477
|
+
<path fill-rule="evenodd" d="M11.47 7.72a.75.75 0 011.06 0l7.5 7.5a.75.75 0 11-1.06 1.06L12 9.31l-6.97 6.97a.75.75 0 01-1.06-1.06l7.5-7.5z" clip-rule="evenodd" />
|
|
1478
|
+
</svg>`,
|
|
1479
|
+
'clock': `
|
|
1480
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1481
|
+
<path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zM12.75 6a.75.75 0 00-1.5 0v6c0 .414.336.75.75.75h4.5a.75.75 0 000-1.5h-3.75V6z" clip-rule="evenodd" />
|
|
1482
|
+
</svg>`,
|
|
1483
|
+
'calendar-month': `
|
|
1484
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"/></svg>`,
|
|
1485
|
+
'calendar-week': `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm360-80h100v-480H520v480Zm-180 0h100v-480H340v480Zm-180 0h100v-480H160v480Zm540 0h100v-480H700v480Z"/></svg>`,
|
|
1486
|
+
'calendar-day': `
|
|
1487
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-280q-33 0-56.5-23.5T120-360v-240q0-33 23.5-56.5T200-680h560q33 0 56.5 23.5T840-600v240q0 33-23.5 56.5T760-280H200Zm0-80h560v-240H200v240Zm-80-400v-80h720v80H120Zm0 640v-80h720v80H120Zm80-480v240-240Z"/></svg>`,
|
|
1488
|
+
'add': `
|
|
1489
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1490
|
+
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
|
|
1491
|
+
</svg>`,
|
|
1492
|
+
'cycle': `
|
|
1493
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M204-318q-22-38-33-78t-11-82q0-134 93-228t227-94h7l-64-64 56-56 160 160-160 160-56-56 64-64h-7q-100 0-170 70.5T240-478q0 26 6 51t18 49l-60 60ZM481-40 321-200l160-160 56 56-64 64h7q100 0 170-70.5T720-482q0-26-6-51t-18-49l60-60q22 38 33 78t11 82q0 134-93 228t-227 94h-7l64 64-56 56Z"/></svg>
|
|
1494
|
+
`,
|
|
1495
|
+
'dot': `
|
|
1496
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1497
|
+
<circle cx="12" cy="12" r="6" />
|
|
1498
|
+
</svg>
|
|
1499
|
+
`
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
class IconComponent {
|
|
1503
|
+
sanitizer = inject(DomSanitizer);
|
|
1504
|
+
// Input requerido tipo Signal
|
|
1505
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
1506
|
+
// Computada: Busca el string y lo sanitiza para evitar ataques XSS
|
|
1507
|
+
svgContent = computed(() => {
|
|
1508
|
+
const svgString = ICONS[this.name()] || '';
|
|
1509
|
+
return this.sanitizer.bypassSecurityTrustHtml(svgString);
|
|
1510
|
+
}, ...(ngDevMode ? [{ debugName: "svgContent" }] : []));
|
|
1511
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: IconComponent, isStandalone: true, selector: "mglon-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `<div class="mglon-icon" [innerHTML]="svgContent()"></div>`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1}.mglon-icon{display:flex;align-items:center;justify-content:center;width:1em;height:1em;min-width:1em;min-height:1em}::ng-deep svg{width:100%;height:100%;fill:currentColor}\n"] });
|
|
1513
|
+
}
|
|
1514
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconComponent, decorators: [{
|
|
1515
|
+
type: Component,
|
|
1516
|
+
args: [{ selector: 'mglon-icon', standalone: true, template: `<div class="mglon-icon" [innerHTML]="svgContent()"></div>`, styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1}.mglon-icon{display:flex;align-items:center;justify-content:center;width:1em;height:1em;min-width:1em;min-height:1em}::ng-deep svg{width:100%;height:100%;fill:currentColor}\n"] }]
|
|
1517
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }] } });
|
|
1518
|
+
|
|
1519
|
+
class ButtonComponent {
|
|
1520
|
+
appereance = input('solid', ...(ngDevMode ? [{ debugName: "appereance" }] : []));
|
|
1521
|
+
color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
1522
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
1523
|
+
rounded = input('md', ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
1524
|
+
density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
1525
|
+
active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
1526
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1527
|
+
elementRef = inject(ElementRef);
|
|
1528
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1529
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: ButtonComponent, isStandalone: true, selector: "button[mglon-button], a[mglon-button]", inputs: { appereance: { classPropertyName: "appereance", publicName: "appereance", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.appereance": "appereance()", "attr.color": "color()", "attr.size": "size()", "attr.rounded": "rounded()", "attr.density": "density()", "attr.active": "active() || null", "attr.disabled": "disabled() || null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;min-width:var(--mglon-button-min-width, 64px);outline:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;text-decoration:none;cursor:pointer;--btn-padding-y: var(--mglon-button-padding-y, 0);--btn-padding-x: var(--mglon-button-padding-x, 16px);--btn-height: var(--mglon-button-height, 40px);--btn-radius: var(--mglon-button-radius, var(--mglon-schedule-radius-md));--btn-font-size: var(--mglon-button-font-size, var(--mglon-schedule-font-size-md, 14px));--btn-border-width: var(--mglon-button-border-width, 2px);--btn-transition: var(--mglon-button-transition, background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), box-shadow .28s var(--mglon-schedule-transition-easing), color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), border-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing));height:var(--btn-height);padding:var(--btn-padding-y) var(--btn-padding-x);border-radius:var(--btn-radius);font-size:var(--btn-font-size);font-family:var(--mglon-schedule-font-family, Roboto, sans-serif);font-weight:var(--mglon-button-font-weight, 500);border:var(--btn-border-width) solid transparent;background-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-button-primary-color, var(--mglon-schedule-on-primary));transition:var(--btn-transition)}:host[rounded=none]{--btn-radius: 0}:host[rounded=sm]{--btn-radius: var(--mglon-schedule-radius-sm)}:host[rounded=md]{--btn-radius: var(--mglon-schedule-radius-md)}:host[rounded=lg]{--btn-radius: var(--mglon-schedule-radius-lg)}:host[rounded=full]{--btn-radius: var(--mglon-schedule-radius-full)}:host[size=sm]{--btn-height: var(--mglon-button-sm-height, 32px);--btn-padding-x: var(--mglon-button-sm-padding-x, 12px);--btn-font-size: var(--mglon-button-sm-font-size, var(--mglon-schedule-font-size-sm, 12px))}:host[size=md]{--btn-height: var(--mglon-button-md-height, 40px);--btn-padding-x: var(--mglon-button-md-padding-x, 16px);--btn-font-size: var(--mglon-button-md-font-size, var(--mglon-schedule-font-size-md, 14px))}:host[size=lg]{--btn-height: var(--mglon-button-lg-height, 48px);--btn-padding-x: var(--mglon-button-lg-padding-x, 24px);--btn-font-size: var(--mglon-button-lg-font-size, var(--mglon-schedule-font-size-lg, 16px))}:host[density=compact]{--btn-padding-x: var(--mglon-button-compact-padding-x, 8px)}:host[appereance=solid]{box-shadow:0 1px 3px #0000001a,0 1px 2px #0000000f}:host[appereance=solid]:hover:not([disabled]){box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;opacity:var(--mglon-button-hover-opacity, .92)}:host[appereance=outline]{background-color:transparent;border-color:currentColor}:host[appereance=outline]:hover:not([disabled]){background-color:var(--mglon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=ghost]{background-color:transparent;border-color:transparent}:host[appereance=ghost]:hover:not([disabled]){background-color:var(--mglon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=link]{background-color:transparent;border-color:transparent;text-decoration:underline;min-width:auto;--btn-padding-x: 4px}:host[appereance=link]:hover:not([disabled]){text-decoration:none}:host[appereance=solid][color=primary]{background-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-button-primary-color, var(--mglon-schedule-on-primary))}:host[appereance=solid][color=secondary]{background-color:var(--mglon-button-secondary-bg, var(--mglon-schedule-surface-3));color:var(--mglon-button-secondary-color, var(--mglon-schedule-on-surface-3))}:host[appereance=solid][color=error]{background-color:var(--mglon-button-error-bg, var(--mglon-schedule-error));color:var(--mglon-button-error-color, var(--mglon-schedule-on-error))}:host[appereance=outline][color=primary]{color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));border-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary))}:host[appereance=outline][color=primary]:hover:not([disabled]){background-color:var(--mglon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=outline][color=secondary]{color:var(--mglon-schedule-text-secondary);border-color:var(--mglon-schedule-text-secondary)}:host[appereance=outline][color=error]{color:var(--mglon-schedule-error);border-color:var(--mglon-schedule-error)}:host[appereance=ghost][color=primary]{color:var(--mglon-schedule-primary)}:host[appereance=ghost][color=primary]:hover:not([disabled]){background-color:var(--mglon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=ghost][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=error]{color:var(--mglon-schedule-error)}:host[appereance=link][color=primary]{color:var(--mglon-schedule-primary)}:host[appereance=link][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=link][color=error]{color:var(--mglon-schedule-error)}:host[active]{background-color:var(--mglon-schedule-primary);color:var(--mglon-schedule-on-primary);font-weight:600}:host[active][appereance=outline],:host[active][appereance=ghost]{background-color:var(--mglon-schedule-selection);color:var(--mglon-schedule-primary)}:host[disabled]{opacity:var(--mglon-schedule-disabled-opacity, .5);cursor:not-allowed;pointer-events:none;box-shadow:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1530
|
+
}
|
|
1531
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
1532
|
+
type: Component,
|
|
1533
|
+
args: [{ selector: 'button[mglon-button], a[mglon-button]', standalone: true, imports: [CommonModule], template: '<ng-content></ng-content>', host: {
|
|
1534
|
+
'[attr.appereance]': 'appereance()',
|
|
1535
|
+
'[attr.color]': 'color()',
|
|
1536
|
+
'[attr.size]': 'size()',
|
|
1537
|
+
'[attr.rounded]': 'rounded()',
|
|
1538
|
+
'[attr.density]': 'density()',
|
|
1539
|
+
'[attr.active]': 'active() || null',
|
|
1540
|
+
'[attr.disabled]': 'disabled() || null'
|
|
1541
|
+
}, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;min-width:var(--mglon-button-min-width, 64px);outline:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;text-decoration:none;cursor:pointer;--btn-padding-y: var(--mglon-button-padding-y, 0);--btn-padding-x: var(--mglon-button-padding-x, 16px);--btn-height: var(--mglon-button-height, 40px);--btn-radius: var(--mglon-button-radius, var(--mglon-schedule-radius-md));--btn-font-size: var(--mglon-button-font-size, var(--mglon-schedule-font-size-md, 14px));--btn-border-width: var(--mglon-button-border-width, 2px);--btn-transition: var(--mglon-button-transition, background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), box-shadow .28s var(--mglon-schedule-transition-easing), color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), border-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing));height:var(--btn-height);padding:var(--btn-padding-y) var(--btn-padding-x);border-radius:var(--btn-radius);font-size:var(--btn-font-size);font-family:var(--mglon-schedule-font-family, Roboto, sans-serif);font-weight:var(--mglon-button-font-weight, 500);border:var(--btn-border-width) solid transparent;background-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-button-primary-color, var(--mglon-schedule-on-primary));transition:var(--btn-transition)}:host[rounded=none]{--btn-radius: 0}:host[rounded=sm]{--btn-radius: var(--mglon-schedule-radius-sm)}:host[rounded=md]{--btn-radius: var(--mglon-schedule-radius-md)}:host[rounded=lg]{--btn-radius: var(--mglon-schedule-radius-lg)}:host[rounded=full]{--btn-radius: var(--mglon-schedule-radius-full)}:host[size=sm]{--btn-height: var(--mglon-button-sm-height, 32px);--btn-padding-x: var(--mglon-button-sm-padding-x, 12px);--btn-font-size: var(--mglon-button-sm-font-size, var(--mglon-schedule-font-size-sm, 12px))}:host[size=md]{--btn-height: var(--mglon-button-md-height, 40px);--btn-padding-x: var(--mglon-button-md-padding-x, 16px);--btn-font-size: var(--mglon-button-md-font-size, var(--mglon-schedule-font-size-md, 14px))}:host[size=lg]{--btn-height: var(--mglon-button-lg-height, 48px);--btn-padding-x: var(--mglon-button-lg-padding-x, 24px);--btn-font-size: var(--mglon-button-lg-font-size, var(--mglon-schedule-font-size-lg, 16px))}:host[density=compact]{--btn-padding-x: var(--mglon-button-compact-padding-x, 8px)}:host[appereance=solid]{box-shadow:0 1px 3px #0000001a,0 1px 2px #0000000f}:host[appereance=solid]:hover:not([disabled]){box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;opacity:var(--mglon-button-hover-opacity, .92)}:host[appereance=outline]{background-color:transparent;border-color:currentColor}:host[appereance=outline]:hover:not([disabled]){background-color:var(--mglon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=ghost]{background-color:transparent;border-color:transparent}:host[appereance=ghost]:hover:not([disabled]){background-color:var(--mglon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=link]{background-color:transparent;border-color:transparent;text-decoration:underline;min-width:auto;--btn-padding-x: 4px}:host[appereance=link]:hover:not([disabled]){text-decoration:none}:host[appereance=solid][color=primary]{background-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-button-primary-color, var(--mglon-schedule-on-primary))}:host[appereance=solid][color=secondary]{background-color:var(--mglon-button-secondary-bg, var(--mglon-schedule-surface-3));color:var(--mglon-button-secondary-color, var(--mglon-schedule-on-surface-3))}:host[appereance=solid][color=error]{background-color:var(--mglon-button-error-bg, var(--mglon-schedule-error));color:var(--mglon-button-error-color, var(--mglon-schedule-on-error))}:host[appereance=outline][color=primary]{color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary));border-color:var(--mglon-button-primary-bg, var(--mglon-schedule-primary))}:host[appereance=outline][color=primary]:hover:not([disabled]){background-color:var(--mglon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=outline][color=secondary]{color:var(--mglon-schedule-text-secondary);border-color:var(--mglon-schedule-text-secondary)}:host[appereance=outline][color=error]{color:var(--mglon-schedule-error);border-color:var(--mglon-schedule-error)}:host[appereance=ghost][color=primary]{color:var(--mglon-schedule-primary)}:host[appereance=ghost][color=primary]:hover:not([disabled]){background-color:var(--mglon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=ghost][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=error]{color:var(--mglon-schedule-error)}:host[appereance=link][color=primary]{color:var(--mglon-schedule-primary)}:host[appereance=link][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=link][color=error]{color:var(--mglon-schedule-error)}:host[active]{background-color:var(--mglon-schedule-primary);color:var(--mglon-schedule-on-primary);font-weight:600}:host[active][appereance=outline],:host[active][appereance=ghost]{background-color:var(--mglon-schedule-selection);color:var(--mglon-schedule-primary)}:host[disabled]{opacity:var(--mglon-schedule-disabled-opacity, .5);cursor:not-allowed;pointer-events:none;box-shadow:none!important}\n"] }]
|
|
1542
|
+
}], propDecorators: { appereance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appereance", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
1543
|
+
|
|
1544
|
+
class IconButtonComponent {
|
|
1545
|
+
appereance = input('ghost', ...(ngDevMode ? [{ debugName: "appereance" }] : []));
|
|
1546
|
+
color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
1547
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
1548
|
+
rounded = input('md', ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
1549
|
+
active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
1550
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1551
|
+
elementRef = inject(ElementRef);
|
|
1552
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1553
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: IconButtonComponent, isStandalone: true, selector: "button[mglon-icon-button], a[mglon-icon-button]", inputs: { appereance: { classPropertyName: "appereance", publicName: "appereance", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.appereance": "appereance()", "attr.color": "color()", "attr.size": "size()", "attr.rounded": "rounded()", "attr.active": "active() || null", "attr.disabled": "disabled() || null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;outline:none;overflow:hidden;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;--icon-btn-size: var(--mglon-icon-button-size, 40px);--icon-btn-icon-size: var(--mglon-icon-button-icon-size, 24px);--icon-btn-radius: var(--mglon-icon-button-radius, 50%);--icon-btn-bg: var(--mglon-icon-button-bg, transparent);--icon-btn-color: var(--mglon-icon-button-color, var(--mglon-schedule-text-secondary));--icon-btn-border: var(--mglon-icon-button-border, 2px solid transparent);--icon-btn-transition: var(--mglon-icon-button-transition, background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), border-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing));width:var(--icon-btn-size);height:var(--icon-btn-size);font-size:var(--icon-btn-icon-size);border-radius:var(--icon-btn-radius);border:var(--icon-btn-border);background-color:var(--icon-btn-bg);color:var(--icon-btn-color);transition:var(--icon-btn-transition)}:host[size=xs]{--icon-btn-size: var(--mglon-icon-button-xs-size, 24px);--icon-btn-icon-size: var(--mglon-icon-button-xs-icon-size, 14px)}:host[size=sm]{--icon-btn-size: var(--mglon-icon-button-sm-size, 32px);--icon-btn-icon-size: var(--mglon-icon-button-sm-icon-size, 18px)}:host[size=md]{--icon-btn-size: var(--mglon-icon-button-md-size, 40px);--icon-btn-icon-size: var(--mglon-icon-button-md-icon-size, 24px)}:host[size=lg]{--icon-btn-size: var(--mglon-icon-button-lg-size, 48px);--icon-btn-icon-size: var(--mglon-icon-button-lg-icon-size, 28px)}:host[appereance=ghost]{background-color:transparent;border-color:transparent}:host[appereance=ghost]:hover:not([disabled]){background-color:var(--mglon-icon-button-hover-bg, rgba(60, 64, 67, .04))}:host[appereance=solid]{box-shadow:0 1px 3px #0000001a,0 1px 2px #0000000f}:host[appereance=solid]:hover:not([disabled]){box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;opacity:var(--mglon-icon-button-hover-opacity, .92)}:host[appereance=outline]{background-color:transparent;border-color:currentColor}:host[appereance=outline]:hover:not([disabled]){background-color:var(--mglon-icon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=ghost][color=primary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=primary]:hover:not([disabled]){color:var(--mglon-schedule-text-primary);background-color:var(--mglon-icon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=ghost][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=error]{color:var(--mglon-schedule-error)}:host[appereance=solid][color=primary]{background-color:var(--mglon-icon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-on-primary))}:host[appereance=solid][color=secondary]{background-color:var(--mglon-icon-button-secondary-bg, var(--mglon-schedule-surface-3));color:var(--mglon-icon-button-secondary-color, var(--mglon-schedule-on-surface-3))}:host[appereance=solid][color=error]{background-color:var(--mglon-icon-button-error-bg, var(--mglon-schedule-error));color:var(--mglon-icon-button-error-color, var(--mglon-schedule-on-error))}:host[appereance=outline][color=primary]{color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-primary));border-color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-primary))}:host[appereance=outline][color=primary]:hover:not([disabled]){background-color:var(--mglon-icon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=outline][color=secondary]{color:var(--mglon-schedule-text-secondary);border-color:var(--mglon-schedule-text-secondary)}:host[appereance=outline][color=error]{color:var(--mglon-schedule-error);border-color:var(--mglon-schedule-error)}:host[active]{color:var(--mglon-schedule-primary);background-color:var(--mglon-schedule-selection);font-weight:600}:host[active][appereance=solid]{background-color:var(--mglon-schedule-primary);color:var(--mglon-schedule-on-primary)}:host[disabled]{opacity:var(--mglon-schedule-disabled-opacity, .5);cursor:not-allowed;pointer-events:none;background-color:transparent!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1554
|
+
}
|
|
1555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconButtonComponent, decorators: [{
|
|
1556
|
+
type: Component,
|
|
1557
|
+
args: [{ selector: 'button[mglon-icon-button], a[mglon-icon-button]', standalone: true, imports: [CommonModule], template: '<ng-content></ng-content>', host: {
|
|
1558
|
+
'[attr.appereance]': 'appereance()',
|
|
1559
|
+
'[attr.color]': 'color()',
|
|
1560
|
+
'[attr.size]': 'size()',
|
|
1561
|
+
'[attr.rounded]': 'rounded()',
|
|
1562
|
+
'[attr.active]': 'active() || null',
|
|
1563
|
+
'[attr.disabled]': 'disabled() || null'
|
|
1564
|
+
}, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;outline:none;overflow:hidden;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;--icon-btn-size: var(--mglon-icon-button-size, 40px);--icon-btn-icon-size: var(--mglon-icon-button-icon-size, 24px);--icon-btn-radius: var(--mglon-icon-button-radius, 50%);--icon-btn-bg: var(--mglon-icon-button-bg, transparent);--icon-btn-color: var(--mglon-icon-button-color, var(--mglon-schedule-text-secondary));--icon-btn-border: var(--mglon-icon-button-border, 2px solid transparent);--icon-btn-transition: var(--mglon-icon-button-transition, background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing), border-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing));width:var(--icon-btn-size);height:var(--icon-btn-size);font-size:var(--icon-btn-icon-size);border-radius:var(--icon-btn-radius);border:var(--icon-btn-border);background-color:var(--icon-btn-bg);color:var(--icon-btn-color);transition:var(--icon-btn-transition)}:host[size=xs]{--icon-btn-size: var(--mglon-icon-button-xs-size, 24px);--icon-btn-icon-size: var(--mglon-icon-button-xs-icon-size, 14px)}:host[size=sm]{--icon-btn-size: var(--mglon-icon-button-sm-size, 32px);--icon-btn-icon-size: var(--mglon-icon-button-sm-icon-size, 18px)}:host[size=md]{--icon-btn-size: var(--mglon-icon-button-md-size, 40px);--icon-btn-icon-size: var(--mglon-icon-button-md-icon-size, 24px)}:host[size=lg]{--icon-btn-size: var(--mglon-icon-button-lg-size, 48px);--icon-btn-icon-size: var(--mglon-icon-button-lg-icon-size, 28px)}:host[appereance=ghost]{background-color:transparent;border-color:transparent}:host[appereance=ghost]:hover:not([disabled]){background-color:var(--mglon-icon-button-hover-bg, rgba(60, 64, 67, .04))}:host[appereance=solid]{box-shadow:0 1px 3px #0000001a,0 1px 2px #0000000f}:host[appereance=solid]:hover:not([disabled]){box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;opacity:var(--mglon-icon-button-hover-opacity, .92)}:host[appereance=outline]{background-color:transparent;border-color:currentColor}:host[appereance=outline]:hover:not([disabled]){background-color:var(--mglon-icon-button-hover-bg, var(--mglon-schedule-hover-bg))}:host[appereance=ghost][color=primary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=primary]:hover:not([disabled]){color:var(--mglon-schedule-text-primary);background-color:var(--mglon-icon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=ghost][color=secondary]{color:var(--mglon-schedule-text-secondary)}:host[appereance=ghost][color=error]{color:var(--mglon-schedule-error)}:host[appereance=solid][color=primary]{background-color:var(--mglon-icon-button-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-on-primary))}:host[appereance=solid][color=secondary]{background-color:var(--mglon-icon-button-secondary-bg, var(--mglon-schedule-surface-3));color:var(--mglon-icon-button-secondary-color, var(--mglon-schedule-on-surface-3))}:host[appereance=solid][color=error]{background-color:var(--mglon-icon-button-error-bg, var(--mglon-schedule-error));color:var(--mglon-icon-button-error-color, var(--mglon-schedule-on-error))}:host[appereance=outline][color=primary]{color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-primary));border-color:var(--mglon-icon-button-primary-color, var(--mglon-schedule-primary))}:host[appereance=outline][color=primary]:hover:not([disabled]){background-color:var(--mglon-icon-button-primary-hover-bg, rgba(103, 58, 183, .04))}:host[appereance=outline][color=secondary]{color:var(--mglon-schedule-text-secondary);border-color:var(--mglon-schedule-text-secondary)}:host[appereance=outline][color=error]{color:var(--mglon-schedule-error);border-color:var(--mglon-schedule-error)}:host[active]{color:var(--mglon-schedule-primary);background-color:var(--mglon-schedule-selection);font-weight:600}:host[active][appereance=solid]{background-color:var(--mglon-schedule-primary);color:var(--mglon-schedule-on-primary)}:host[disabled]{opacity:var(--mglon-schedule-disabled-opacity, .5);cursor:not-allowed;pointer-events:none;background-color:transparent!important}\n"] }]
|
|
1565
|
+
}], propDecorators: { appereance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appereance", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
1566
|
+
|
|
1567
|
+
class ButtonGroupComponent {
|
|
1568
|
+
type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
1569
|
+
density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
1570
|
+
appereance = input('solid', ...(ngDevMode ? [{ debugName: "appereance" }] : []));
|
|
1571
|
+
rounded = input('full', ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
1572
|
+
orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : []));
|
|
1573
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1574
|
+
// Queries for projected content
|
|
1575
|
+
buttons = contentChildren(ButtonComponent, ...(ngDevMode ? [{ debugName: "buttons" }] : []));
|
|
1576
|
+
iconButtons = contentChildren(IconButtonComponent, ...(ngDevMode ? [{ debugName: "iconButtons" }] : []));
|
|
1577
|
+
indicatorStyle = signal({}, ...(ngDevMode ? [{ debugName: "indicatorStyle" }] : []));
|
|
1578
|
+
constructor() {
|
|
1579
|
+
// Effect to update selection indicator position
|
|
1580
|
+
effect(() => {
|
|
1581
|
+
const allButtons = [...this.buttons(), ...this.iconButtons()];
|
|
1582
|
+
const activeButton = allButtons.find(btn => btn.active() && !btn.disabled());
|
|
1583
|
+
if (activeButton && activeButton.elementRef) {
|
|
1584
|
+
const element = activeButton.elementRef.nativeElement;
|
|
1585
|
+
// Calculate style based on the active element's position and size
|
|
1586
|
+
const rounded = this.rounded();
|
|
1587
|
+
this.indicatorStyle.set({
|
|
1588
|
+
'width': `${element.offsetWidth}px`,
|
|
1589
|
+
'height': `${element.offsetHeight}px`,
|
|
1590
|
+
'transform': `translate(${element.offsetLeft}px, ${element.offsetTop}px)`,
|
|
1591
|
+
'opacity': '1'
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
else {
|
|
1595
|
+
this.indicatorStyle.set({ 'opacity': '0' });
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
// Effect to propagate disabled state to child buttons
|
|
1599
|
+
effect(() => {
|
|
1600
|
+
const isGroupDisabled = this.disabled();
|
|
1601
|
+
const allButtons = [...this.buttons(), ...this.iconButtons()];
|
|
1602
|
+
allButtons.forEach(btn => {
|
|
1603
|
+
if (btn.elementRef) {
|
|
1604
|
+
const element = btn.elementRef.nativeElement;
|
|
1605
|
+
if (isGroupDisabled) {
|
|
1606
|
+
element.setAttribute('disabled', '');
|
|
1607
|
+
}
|
|
1608
|
+
else if (!btn.disabled()) {
|
|
1609
|
+
element.removeAttribute('disabled');
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1616
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.6", type: ButtonGroupComponent, isStandalone: true, selector: "mglon-button-group", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, appereance: { classPropertyName: "appereance", publicName: "appereance", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "\"group\"", "attr.rounded": "rounded()", "attr.type": "type()", "attr.orientation": "orientation()", "attr.appereance": "appereance()", "attr.density": "density()", "attr.disabled": "disabled() || null" } }, queries: [{ propertyName: "buttons", predicate: ButtonComponent, isSignal: true }, { propertyName: "iconButtons", predicate: IconButtonComponent, isSignal: true }], ngImport: i0, template: "<div class=\"selection-indicator\" [style]=\"indicatorStyle()\"></div>\n<ng-content></ng-content>", styles: [":host{display:inline-flex;position:relative;overflow:hidden;z-index:0;gap:var(--mglon-button-group-gap, 0);flex-shrink:0;background-color:var(--mglon-button-group-background, var(--mglon-schedule-surface-1));border:var(--mglon-button-group-border-width, 2px) solid var(--mglon-button-group-border-color, transparent);padding:var(--mglon-button-group-padding, var(--mglon-schedule-padding-xs));border-radius:var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full))}:host[rounded=none]{--mglon-button-group-border-radius: 0}:host[rounded=sm]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-sm)}:host[rounded=md]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-md)}:host[rounded=lg]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-lg)}:host[rounded=full]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-full)}:host[orientation=vertical]{flex-direction:column}:host[appereance=outline]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: var(--mglon-schedule-primary-200)}:host[appereance=ghost]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: transparent;--mglon-button-group-padding: 0px}:host[appereance=link]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: transparent;--mglon-button-group-padding: 0px;--mglon-button-group-gap: var(--mglon-schedule-spacing-sm)}:host[density=compact]{--mglon-button-group-padding: 0px}:host[density=compact][appereance=link]{--mglon-button-group-gap: var(--mglon-schedule-spacing-xs)}:host[disabled]{opacity:var(--mglon-button-group-disabled-opacity, .5);cursor:not-allowed;pointer-events:none}:host[disabled] .selection-indicator{pointer-events:none}:host[disabled] button,:host[disabled] a{pointer-events:none}:host .selection-indicator{position:absolute;top:0;left:0;background-color:var(--mglon-button-group-indicator-background, var(--mglon-schedule-primary));border-radius:calc(var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full)) - var(--mglon-button-group-padding, var(--mglon-schedule-padding-xs)));z-index:0;transition:all var(--mglon-button-group-indicator-transition-duration, var(--mglon-schedule-transition-duration)) cubic-bezier(.16,1,.3,1)}:host ::ng-deep>button[mglon-button],:host ::ng-deep>button[mglon-icon-button],:host ::ng-deep>a[mglon-button],:host ::ng-deep>a[mglon-icon-button]{background-color:transparent!important;border:none!important;margin:0!important;position:relative;z-index:1;border-radius:var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full))!important;color:var(--mglon-button-group-button-color, var(--mglon-schedule-on-surface-1));transition:color var(--mglon-button-group-transition-duration, var(--mglon-schedule-transition-duration)) ease}:host ::ng-deep>button[mglon-button][active],:host ::ng-deep>button[mglon-icon-button][active],:host ::ng-deep>a[mglon-button][active],:host ::ng-deep>a[mglon-icon-button][active]{color:var(--mglon-button-group-button-active-color, var(--mglon-schedule-on-primary))!important;font-weight:var(--mglon-button-group-button-active-font-weight, 500)}:host ::ng-deep>button[mglon-button][disabled],:host ::ng-deep>button[mglon-icon-button][disabled],:host ::ng-deep>a[mglon-button][disabled],:host ::ng-deep>a[mglon-icon-button][disabled]{opacity:var(--mglon-button-group-disabled-opacity, .5);cursor:not-allowed;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1617
|
+
}
|
|
1618
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ButtonGroupComponent, decorators: [{
|
|
1619
|
+
type: Component,
|
|
1620
|
+
args: [{ selector: 'mglon-button-group', standalone: true, imports: [CommonModule], host: {
|
|
1621
|
+
'[attr.role]': '"group"',
|
|
1622
|
+
'[attr.rounded]': 'rounded()',
|
|
1623
|
+
'[attr.type]': 'type()',
|
|
1624
|
+
'[attr.orientation]': 'orientation()',
|
|
1625
|
+
'[attr.appereance]': 'appereance()',
|
|
1626
|
+
'[attr.density]': 'density()',
|
|
1627
|
+
'[attr.disabled]': 'disabled() || null'
|
|
1628
|
+
}, template: "<div class=\"selection-indicator\" [style]=\"indicatorStyle()\"></div>\n<ng-content></ng-content>", styles: [":host{display:inline-flex;position:relative;overflow:hidden;z-index:0;gap:var(--mglon-button-group-gap, 0);flex-shrink:0;background-color:var(--mglon-button-group-background, var(--mglon-schedule-surface-1));border:var(--mglon-button-group-border-width, 2px) solid var(--mglon-button-group-border-color, transparent);padding:var(--mglon-button-group-padding, var(--mglon-schedule-padding-xs));border-radius:var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full))}:host[rounded=none]{--mglon-button-group-border-radius: 0}:host[rounded=sm]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-sm)}:host[rounded=md]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-md)}:host[rounded=lg]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-lg)}:host[rounded=full]{--mglon-button-group-border-radius: var(--mglon-schedule-radius-full)}:host[orientation=vertical]{flex-direction:column}:host[appereance=outline]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: var(--mglon-schedule-primary-200)}:host[appereance=ghost]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: transparent;--mglon-button-group-padding: 0px}:host[appereance=link]{--mglon-button-group-background: transparent;--mglon-button-group-border-color: transparent;--mglon-button-group-padding: 0px;--mglon-button-group-gap: var(--mglon-schedule-spacing-sm)}:host[density=compact]{--mglon-button-group-padding: 0px}:host[density=compact][appereance=link]{--mglon-button-group-gap: var(--mglon-schedule-spacing-xs)}:host[disabled]{opacity:var(--mglon-button-group-disabled-opacity, .5);cursor:not-allowed;pointer-events:none}:host[disabled] .selection-indicator{pointer-events:none}:host[disabled] button,:host[disabled] a{pointer-events:none}:host .selection-indicator{position:absolute;top:0;left:0;background-color:var(--mglon-button-group-indicator-background, var(--mglon-schedule-primary));border-radius:calc(var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full)) - var(--mglon-button-group-padding, var(--mglon-schedule-padding-xs)));z-index:0;transition:all var(--mglon-button-group-indicator-transition-duration, var(--mglon-schedule-transition-duration)) cubic-bezier(.16,1,.3,1)}:host ::ng-deep>button[mglon-button],:host ::ng-deep>button[mglon-icon-button],:host ::ng-deep>a[mglon-button],:host ::ng-deep>a[mglon-icon-button]{background-color:transparent!important;border:none!important;margin:0!important;position:relative;z-index:1;border-radius:var(--mglon-button-group-border-radius, var(--mglon-schedule-radius-full))!important;color:var(--mglon-button-group-button-color, var(--mglon-schedule-on-surface-1));transition:color var(--mglon-button-group-transition-duration, var(--mglon-schedule-transition-duration)) ease}:host ::ng-deep>button[mglon-button][active],:host ::ng-deep>button[mglon-icon-button][active],:host ::ng-deep>a[mglon-button][active],:host ::ng-deep>a[mglon-icon-button][active]{color:var(--mglon-button-group-button-active-color, var(--mglon-schedule-on-primary))!important;font-weight:var(--mglon-button-group-button-active-font-weight, 500)}:host ::ng-deep>button[mglon-button][disabled],:host ::ng-deep>button[mglon-icon-button][disabled],:host ::ng-deep>a[mglon-button][disabled],:host ::ng-deep>a[mglon-icon-button][disabled]{opacity:var(--mglon-button-group-disabled-opacity, .5);cursor:not-allowed;pointer-events:none}\n"] }]
|
|
1629
|
+
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], appereance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appereance", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], buttons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ButtonComponent), { isSignal: true }] }], iconButtons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconButtonComponent), { isSignal: true }] }] } });
|
|
1630
|
+
|
|
1631
|
+
class HeaderSchedule {
|
|
1632
|
+
platformId = inject(PLATFORM_ID);
|
|
1633
|
+
elementRef = inject(ElementRef);
|
|
1634
|
+
resizeObserver;
|
|
1635
|
+
calendarStore = inject(CalendarStore);
|
|
1636
|
+
// Header UI Configuration from Store
|
|
1637
|
+
buttonGroupAppearance = computed(() => this.calendarStore.uiConfig().header.buttonGroup.appearance, ...(ngDevMode ? [{ debugName: "buttonGroupAppearance" }] : []));
|
|
1638
|
+
buttonGroupRounded = computed(() => this.calendarStore.uiConfig().header.buttonGroup.rounded, ...(ngDevMode ? [{ debugName: "buttonGroupRounded" }] : []));
|
|
1639
|
+
buttonGroupDensity = computed(() => this.calendarStore.uiConfig().header.buttonGroup.density, ...(ngDevMode ? [{ debugName: "buttonGroupDensity" }] : []));
|
|
1640
|
+
iconButtonRounded = computed(() => this.calendarStore.uiConfig().header.iconButtons.rounded, ...(ngDevMode ? [{ debugName: "iconButtonRounded" }] : []));
|
|
1641
|
+
todayButtonRounded = computed(() => this.calendarStore.uiConfig().header.todayButton.rounded, ...(ngDevMode ? [{ debugName: "todayButtonRounded" }] : []));
|
|
1642
|
+
todayButtonAppearance = computed(() => this.calendarStore.uiConfig().header.todayButton.appearance, ...(ngDevMode ? [{ debugName: "todayButtonAppearance" }] : []));
|
|
1643
|
+
// Inputs
|
|
1644
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1645
|
+
activeView = input.required(...(ngDevMode ? [{ debugName: "activeView" }] : []));
|
|
1646
|
+
views = input(['month', 'week', 'day', 'resource'], ...(ngDevMode ? [{ debugName: "views" }] : []));
|
|
1647
|
+
editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : []));
|
|
1648
|
+
// Outputs
|
|
1649
|
+
next = output();
|
|
1650
|
+
prev = output();
|
|
1651
|
+
today = output();
|
|
1652
|
+
viewChange = output();
|
|
1653
|
+
add = output();
|
|
1654
|
+
viewIcons = {
|
|
1655
|
+
'month': 'calendar-month',
|
|
1656
|
+
'week': 'calendar-week',
|
|
1657
|
+
'day': 'calendar-day',
|
|
1658
|
+
'resource': 'calendar-week', // Fallback
|
|
1659
|
+
'list': 'calendar-month' // Fallback
|
|
1660
|
+
};
|
|
1661
|
+
// Responsive signal to track container size
|
|
1662
|
+
isSmallScreen = signal(false, ...(ngDevMode ? [{ debugName: "isSmallScreen" }] : []));
|
|
1663
|
+
constructor() {
|
|
1664
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
1665
|
+
// Use ResizeObserver to watch the component's own width
|
|
1666
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1667
|
+
for (const entry of entries) {
|
|
1668
|
+
const width = entry.contentRect.width;
|
|
1669
|
+
// Match the 'sm' breakpoint (767.98px)
|
|
1670
|
+
this.isSmallScreen.set(width <= 767.98);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
// Start observing the host element
|
|
1674
|
+
this.resizeObserver.observe(this.elementRef.nativeElement);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
ngOnDestroy() {
|
|
1678
|
+
// Clean up the observer
|
|
1679
|
+
this.resizeObserver?.disconnect();
|
|
1680
|
+
}
|
|
1681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: HeaderSchedule, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1682
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: HeaderSchedule, isStandalone: true, selector: "mglon-header-schedule", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, activeView: { classPropertyName: "activeView", publicName: "activeView", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { next: "next", prev: "prev", today: "today", viewChange: "viewChange", add: "add" }, ngImport: i0, template: "<div class=\"mglon-header\">\n\n <!-- Row 2: Navigation + Title -->\n <div class=\"mglon-header__nav\">\n <div class=\"mglon-header__nav__actions\">\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"prev.emit()\">\n <mglon-icon name=\"chevron-left\"></mglon-icon>\n </button>\n <button mglon-button [appereance]=\"todayButtonAppearance()\" [rounded]=\"todayButtonRounded()\"\n (click)=\"today.emit()\">\n Today\n </button>\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"next.emit()\">\n <mglon-icon name=\"chevron-right\"></mglon-icon>\n </button>\n </div>\n\n <h2 class=\"mglon-header__title\">{{ title() }}</h2>\n </div>\n\n <div class=\"mglon-header__actions\">\n @if (isSmallScreen()) {\n <!-- Icon buttons for small screens -->\n <mglon-button-group type=\"icon\" [disabled]=\"false\" [appereance]=\"buttonGroupAppearance()\"\n [rounded]=\"buttonGroupRounded()\" [density]=\"buttonGroupDensity()\">\n @for (view of views(); track view) {\n <button mglon-icon-button appereance=\"ghost\" [active]=\"activeView() === view\" (click)=\"viewChange.emit(view)\">\n <mglon-icon [name]=\"viewIcons[view]\" size=\"20px\"></mglon-icon>\n </button>\n }\n </mglon-button-group>\n } @else {\n <!-- Text buttons for larger screens -->\n <mglon-button-group type=\"text\" [disabled]=\"false\" [appereance]=\"buttonGroupAppearance()\"\n [rounded]=\"buttonGroupRounded()\" [density]=\"buttonGroupDensity()\">\n @for (view of views(); track view) {\n <button mglon-button appereance=\"ghost\" [active]=\"activeView() === view\" (click)=\"viewChange.emit(view)\">\n {{ view }}\n </button>\n }\n </mglon-button-group>\n }\n\n @if (editable()) {\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"add.emit()\">\n <mglon-icon name=\"add\"></mglon-icon>\n </button>\n }\n </div>\n\n</div>", styles: [":host{display:block;--header-bg: var(--mglon-header-bg, var(--mglon-schedule-surface));--header-padding: var(--mglon-header-padding, var(--mglon-schedule-spacing-md));--header-gap: var(--mglon-header-gap, var(--mglon-schedule-spacing-md));--header-title-size: var(--mglon-header-title-size, 1.5rem);--header-title-weight: var(--mglon-header-title-weight, 500);--header-title-color: var(--mglon-header-title-color, var(--mglon-schedule-text-primary));--header-title-mobile-size: var(--mglon-header-title-mobile-size, 1.25rem);--header-nav-gap: var(--mglon-header-nav-gap, var(--mglon-schedule-spacing-sm));background-color:var(--header-bg);container-type:inline-size;container-name:header}.mglon-header{display:flex;align-items:center;gap:var(--header-gap);padding:var(--header-padding)}@container (max-width: 767.98px){.mglon-header{flex-direction:column;align-items:stretch}}.mglon-header__nav{display:flex;align-items:center;flex-grow:1;gap:var(--header-nav-gap);justify-self:start}@container (max-width: 767.98px){.mglon-header__nav{justify-content:space-between;flex-grow:0;width:100%}}.mglon-header__nav__actions{display:flex;align-items:center;gap:var(--header-nav-gap);justify-self:start;flex-grow:0}@container (max-width: 767.98px){.mglon-header__nav__actions{justify-content:space-between}}.mglon-header__title{margin:0;font-family:var(--mglon-schedule-font-family);font-size:var(--header-title-size);font-weight:var(--header-title-weight);color:var(--header-title-color);justify-self:center;text-align:center}@container (max-width: 767.98px){.mglon-header__title{order:-1;font-size:var(--header-title-mobile-size)}}.mglon-header__actions{display:flex;gap:var(--header-gap);align-items:center;justify-self:end}@container (max-width: 767.98px){.mglon-header__actions{justify-content:space-between;width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "mglon-icon", inputs: ["name"] }, { kind: "component", type: ButtonGroupComponent, selector: "mglon-button-group", inputs: ["type", "density", "appereance", "rounded", "orientation", "disabled"] }, { kind: "component", type: ButtonComponent, selector: "button[mglon-button], a[mglon-button]", inputs: ["appereance", "color", "size", "rounded", "density", "active", "disabled"] }, { kind: "component", type: IconButtonComponent, selector: "button[mglon-icon-button], a[mglon-icon-button]", inputs: ["appereance", "color", "size", "rounded", "active", "disabled"] }] });
|
|
1683
|
+
}
|
|
1684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: HeaderSchedule, decorators: [{
|
|
1685
|
+
type: Component,
|
|
1686
|
+
args: [{ selector: 'mglon-header-schedule', standalone: true, imports: [CommonModule, IconComponent, ButtonGroupComponent, ButtonComponent, IconButtonComponent], template: "<div class=\"mglon-header\">\n\n <!-- Row 2: Navigation + Title -->\n <div class=\"mglon-header__nav\">\n <div class=\"mglon-header__nav__actions\">\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"prev.emit()\">\n <mglon-icon name=\"chevron-left\"></mglon-icon>\n </button>\n <button mglon-button [appereance]=\"todayButtonAppearance()\" [rounded]=\"todayButtonRounded()\"\n (click)=\"today.emit()\">\n Today\n </button>\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"next.emit()\">\n <mglon-icon name=\"chevron-right\"></mglon-icon>\n </button>\n </div>\n\n <h2 class=\"mglon-header__title\">{{ title() }}</h2>\n </div>\n\n <div class=\"mglon-header__actions\">\n @if (isSmallScreen()) {\n <!-- Icon buttons for small screens -->\n <mglon-button-group type=\"icon\" [disabled]=\"false\" [appereance]=\"buttonGroupAppearance()\"\n [rounded]=\"buttonGroupRounded()\" [density]=\"buttonGroupDensity()\">\n @for (view of views(); track view) {\n <button mglon-icon-button appereance=\"ghost\" [active]=\"activeView() === view\" (click)=\"viewChange.emit(view)\">\n <mglon-icon [name]=\"viewIcons[view]\" size=\"20px\"></mglon-icon>\n </button>\n }\n </mglon-button-group>\n } @else {\n <!-- Text buttons for larger screens -->\n <mglon-button-group type=\"text\" [disabled]=\"false\" [appereance]=\"buttonGroupAppearance()\"\n [rounded]=\"buttonGroupRounded()\" [density]=\"buttonGroupDensity()\">\n @for (view of views(); track view) {\n <button mglon-button appereance=\"ghost\" [active]=\"activeView() === view\" (click)=\"viewChange.emit(view)\">\n {{ view }}\n </button>\n }\n </mglon-button-group>\n }\n\n @if (editable()) {\n <button mglon-icon-button [rounded]=\"iconButtonRounded()\" (click)=\"add.emit()\">\n <mglon-icon name=\"add\"></mglon-icon>\n </button>\n }\n </div>\n\n</div>", styles: [":host{display:block;--header-bg: var(--mglon-header-bg, var(--mglon-schedule-surface));--header-padding: var(--mglon-header-padding, var(--mglon-schedule-spacing-md));--header-gap: var(--mglon-header-gap, var(--mglon-schedule-spacing-md));--header-title-size: var(--mglon-header-title-size, 1.5rem);--header-title-weight: var(--mglon-header-title-weight, 500);--header-title-color: var(--mglon-header-title-color, var(--mglon-schedule-text-primary));--header-title-mobile-size: var(--mglon-header-title-mobile-size, 1.25rem);--header-nav-gap: var(--mglon-header-nav-gap, var(--mglon-schedule-spacing-sm));background-color:var(--header-bg);container-type:inline-size;container-name:header}.mglon-header{display:flex;align-items:center;gap:var(--header-gap);padding:var(--header-padding)}@container (max-width: 767.98px){.mglon-header{flex-direction:column;align-items:stretch}}.mglon-header__nav{display:flex;align-items:center;flex-grow:1;gap:var(--header-nav-gap);justify-self:start}@container (max-width: 767.98px){.mglon-header__nav{justify-content:space-between;flex-grow:0;width:100%}}.mglon-header__nav__actions{display:flex;align-items:center;gap:var(--header-nav-gap);justify-self:start;flex-grow:0}@container (max-width: 767.98px){.mglon-header__nav__actions{justify-content:space-between}}.mglon-header__title{margin:0;font-family:var(--mglon-schedule-font-family);font-size:var(--header-title-size);font-weight:var(--header-title-weight);color:var(--header-title-color);justify-self:center;text-align:center}@container (max-width: 767.98px){.mglon-header__title{order:-1;font-size:var(--header-title-mobile-size)}}.mglon-header__actions{display:flex;gap:var(--header-gap);align-items:center;justify-self:end}@container (max-width: 767.98px){.mglon-header__actions{justify-content:space-between;width:100%}}\n"] }]
|
|
1687
|
+
}], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], activeView: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeView", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], next: [{ type: i0.Output, args: ["next"] }], prev: [{ type: i0.Output, args: ["prev"] }], today: [{ type: i0.Output, args: ["today"] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], add: [{ type: i0.Output, args: ["add"] }] } });
|
|
1688
|
+
|
|
1689
|
+
class MonthHeader {
|
|
1690
|
+
daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
1691
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1692
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: MonthHeader, isStandalone: true, selector: "mglon-month-header", ngImport: i0, template: "<div class=\"mglon-month-header-container\">\n <!-- Spacer to align with toggle column -->\n <div class=\"mglon-month-header__spacer\"></div>\n\n <div class=\"mglon-month-header\">\n @for (day of daysOfWeek; track $index) {\n <div>{{ day }}</div>\n }\n </div>\n</div>", styles: [":host{display:block;width:100%;--month-header-bg: var(--mglon-month-header-bg, var(--mglon-schedule-surface));--month-header-border: var(--mglon-month-header-border, 1px solid var(--mglon-schedule-border));--month-header-padding: var(--mglon-month-header-padding, var(--mglon-schedule-padding-md));--month-header-text-color: var(--mglon-month-header-text-color, var(--mglon-schedule-text-primary));--month-header-font-size: var(--mglon-month-header-font-size, var(--mglon-schedule-font-size-md));--month-header-font-weight: var(--mglon-month-header-font-weight, 500);--month-header-z-index: var(--mglon-month-header-z-index, var(--mglon-schedule-z-sticky))}.mglon-month-header-container{display:flex;width:100%;position:sticky;top:0;z-index:var(--month-header-z-index);background-color:var(--month-header-bg)}.mglon-month-header__spacer{width:32px;min-width:32px;background-color:var(--month-header-bg)}.mglon-month-header{flex:1;display:grid;grid-template-columns:repeat(7,1fr);gap:0;border-bottom:var(--month-header-border)}.mglon-month-header>div{padding:var(--month-header-padding);text-align:center;font-weight:var(--month-header-font-weight);font-size:var(--month-header-font-size);color:var(--month-header-text-color)}.mglon-month-header>div:last-child{border-right:none}\n"] });
|
|
1693
|
+
}
|
|
1694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthHeader, decorators: [{
|
|
1695
|
+
type: Component,
|
|
1696
|
+
args: [{ selector: 'mglon-month-header', imports: [], template: "<div class=\"mglon-month-header-container\">\n <!-- Spacer to align with toggle column -->\n <div class=\"mglon-month-header__spacer\"></div>\n\n <div class=\"mglon-month-header\">\n @for (day of daysOfWeek; track $index) {\n <div>{{ day }}</div>\n }\n </div>\n</div>", styles: [":host{display:block;width:100%;--month-header-bg: var(--mglon-month-header-bg, var(--mglon-schedule-surface));--month-header-border: var(--mglon-month-header-border, 1px solid var(--mglon-schedule-border));--month-header-padding: var(--mglon-month-header-padding, var(--mglon-schedule-padding-md));--month-header-text-color: var(--mglon-month-header-text-color, var(--mglon-schedule-text-primary));--month-header-font-size: var(--mglon-month-header-font-size, var(--mglon-schedule-font-size-md));--month-header-font-weight: var(--mglon-month-header-font-weight, 500);--month-header-z-index: var(--mglon-month-header-z-index, var(--mglon-schedule-z-sticky))}.mglon-month-header-container{display:flex;width:100%;position:sticky;top:0;z-index:var(--month-header-z-index);background-color:var(--month-header-bg)}.mglon-month-header__spacer{width:32px;min-width:32px;background-color:var(--month-header-bg)}.mglon-month-header{flex:1;display:grid;grid-template-columns:repeat(7,1fr);gap:0;border-bottom:var(--month-header-border)}.mglon-month-header>div{padding:var(--month-header-padding);text-align:center;font-weight:var(--month-header-font-weight);font-size:var(--month-header-font-size);color:var(--month-header-text-color)}.mglon-month-header>div:last-child{border-right:none}\n"] }]
|
|
1697
|
+
}] });
|
|
1698
|
+
|
|
1699
|
+
class MonthDayIndicator {
|
|
1700
|
+
day = input.required(...(ngDevMode ? [{ debugName: "day" }] : []));
|
|
1701
|
+
isToday = computed(() => this.day().date.toDateString() === new Date().toDateString(), ...(ngDevMode ? [{ debugName: "isToday" }] : []));
|
|
1702
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthDayIndicator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1703
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: MonthDayIndicator, isStandalone: true, selector: "mglon-month-day-indicator", inputs: { day: { classPropertyName: "day", publicName: "day", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"mglon-month-day-indicator__container\">\n <span [class.today]=\"isToday()\">{{ day().day }}</span>\n</div>", styles: [".mglon-month-day-indicator__container{font-size:var(--month-cell-day-number-size);font-weight:var(--month-cell-day-number-weight);line-height:1}.mglon-month-day-indicator__container span{color:var(--mglon-month-day-indicator-text-color, var(--mglon-schedule-text-secondary));padding:var(--mglon-month-day-indicator-padding, var(--mglon-schedule-padding-xs));border-radius:var(--mglon-month-day-indicator-border-radius, var(--mglon-schedule-radius-full))}.mglon-month-day-indicator__container .today{color:var(--mglon-month-day-indicator-today-background-color, var(--mglon-schedule-error));font-weight:600}\n"] });
|
|
1704
|
+
}
|
|
1705
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthDayIndicator, decorators: [{
|
|
1706
|
+
type: Component,
|
|
1707
|
+
args: [{ selector: 'mglon-month-day-indicator', imports: [], template: "<div class=\"mglon-month-day-indicator__container\">\n <span [class.today]=\"isToday()\">{{ day().day }}</span>\n</div>", styles: [".mglon-month-day-indicator__container{font-size:var(--month-cell-day-number-size);font-weight:var(--month-cell-day-number-weight);line-height:1}.mglon-month-day-indicator__container span{color:var(--mglon-month-day-indicator-text-color, var(--mglon-schedule-text-secondary));padding:var(--mglon-month-day-indicator-padding, var(--mglon-schedule-padding-xs));border-radius:var(--mglon-month-day-indicator-border-radius, var(--mglon-schedule-radius-full))}.mglon-month-day-indicator__container .today{color:var(--mglon-month-day-indicator-today-background-color, var(--mglon-schedule-error));font-weight:600}\n"] }]
|
|
1708
|
+
}], propDecorators: { day: [{ type: i0.Input, args: [{ isSignal: true, alias: "day", required: true }] }] } });
|
|
1709
|
+
|
|
1710
|
+
class MonthCell {
|
|
1711
|
+
store = inject(CalendarStore);
|
|
1712
|
+
// Input for the day data
|
|
1713
|
+
day = input.required(...(ngDevMode ? [{ debugName: "day" }] : []));
|
|
1714
|
+
/** Whether a slot is currently being dragged or resized over this cell */
|
|
1715
|
+
isDragOver = computed(() => {
|
|
1716
|
+
const dragState = this.store.dragState();
|
|
1717
|
+
const resizeState = this.store.resizeState();
|
|
1718
|
+
const hoverDate = dragState.hoverDate || resizeState.hoverDate;
|
|
1719
|
+
if (!hoverDate)
|
|
1720
|
+
return false;
|
|
1721
|
+
return hoverDate.getTime() === this.day().date.getTime();
|
|
1722
|
+
}, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
1723
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthCell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1724
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: MonthCell, isStandalone: true, selector: "mglon-month-cell", inputs: { day: { classPropertyName: "day", publicName: "day", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"mglon-month-cell\" [class.mglon-month-cell--other-month]=\"!day().isCurrentMonth\"\n [class.mglon-month-cell--current-month]=\"day().isCurrentMonth\" [class.mglon-month-cell--drag-over]=\"isDragOver()\"\n [attr.data-mglon-date]=\"day().date.getTime()\">\n <mglon-month-day-indicator [day]=\"day()\"></mglon-month-day-indicator>\n</div>", styles: [":host{display:block;height:100%;width:100%;--month-cell-padding: var(--mglon-month-cell-padding, var(--mglon-schedule-padding-sm));--month-cell-bg: var(--mglon-month-cell-bg, var(--mglon-schedule-surface));--month-cell-hover-bg: var(--mglon-month-cell-hover-bg, var(--mglon-schedule-surface-1));--month-cell-text-primary: var(--mglon-month-cell-text-primary, var(--mglon-schedule-text-primary));--month-cell-text-secondary: var(--mglon-month-cell-text-secondary, var(--mglon-schedule-text-secondary));--month-cell-day-number-size: var(--mglon-month-cell-day-number-size, var(--mglon-schedule-font-size-sm));--month-cell-day-number-weight: var(--mglon-month-cell-day-number-weight, 500);--month-cell-transition-duration: var(--mglon-month-cell-transition-duration, var(--mglon-schedule-transition-duration))}.mglon-month-cell{position:relative;padding:var(--month-cell-padding);background-color:var(--month-cell-bg);overflow:hidden;height:100%;box-sizing:border-box;transition-duration:var(--month-cell-transition-duration)}.mglon-month-cell:last-child{border-right:none}.mglon-month-cell:hover,.mglon-month-cell--drag-over{background-color:var(--month-cell-hover-bg)}.mglon-month-cell--drag-over{box-shadow:inset 0 0 0 2px var(--mglon-schedule-primary);z-index:1}.mglon-month-cell--other-month .mglon-month-cell__day-number{color:var(--month-cell-text-secondary);opacity:.5}.mglon-month-cell--current-month .mglon-month-cell__day-number{color:var(--month-cell-text-primary)}\n"], dependencies: [{ kind: "component", type: MonthDayIndicator, selector: "mglon-month-day-indicator", inputs: ["day"] }] });
|
|
1725
|
+
}
|
|
1726
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthCell, decorators: [{
|
|
1727
|
+
type: Component,
|
|
1728
|
+
args: [{ selector: 'mglon-month-cell', standalone: true, imports: [MonthDayIndicator], template: "<div class=\"mglon-month-cell\" [class.mglon-month-cell--other-month]=\"!day().isCurrentMonth\"\n [class.mglon-month-cell--current-month]=\"day().isCurrentMonth\" [class.mglon-month-cell--drag-over]=\"isDragOver()\"\n [attr.data-mglon-date]=\"day().date.getTime()\">\n <mglon-month-day-indicator [day]=\"day()\"></mglon-month-day-indicator>\n</div>", styles: [":host{display:block;height:100%;width:100%;--month-cell-padding: var(--mglon-month-cell-padding, var(--mglon-schedule-padding-sm));--month-cell-bg: var(--mglon-month-cell-bg, var(--mglon-schedule-surface));--month-cell-hover-bg: var(--mglon-month-cell-hover-bg, var(--mglon-schedule-surface-1));--month-cell-text-primary: var(--mglon-month-cell-text-primary, var(--mglon-schedule-text-primary));--month-cell-text-secondary: var(--mglon-month-cell-text-secondary, var(--mglon-schedule-text-secondary));--month-cell-day-number-size: var(--mglon-month-cell-day-number-size, var(--mglon-schedule-font-size-sm));--month-cell-day-number-weight: var(--mglon-month-cell-day-number-weight, 500);--month-cell-transition-duration: var(--mglon-month-cell-transition-duration, var(--mglon-schedule-transition-duration))}.mglon-month-cell{position:relative;padding:var(--month-cell-padding);background-color:var(--month-cell-bg);overflow:hidden;height:100%;box-sizing:border-box;transition-duration:var(--month-cell-transition-duration)}.mglon-month-cell:last-child{border-right:none}.mglon-month-cell:hover,.mglon-month-cell--drag-over{background-color:var(--month-cell-hover-bg)}.mglon-month-cell--drag-over{box-shadow:inset 0 0 0 2px var(--mglon-schedule-primary);z-index:1}.mglon-month-cell--other-month .mglon-month-cell__day-number{color:var(--month-cell-text-secondary);opacity:.5}.mglon-month-cell--current-month .mglon-month-cell__day-number{color:var(--month-cell-text-primary)}\n"] }]
|
|
1729
|
+
}], propDecorators: { day: [{ type: i0.Input, args: [{ isSignal: true, alias: "day", required: true }] }] } });
|
|
1730
|
+
|
|
1731
|
+
class ZigzagDirective {
|
|
1732
|
+
/**
|
|
1733
|
+
* Side(s) to apply the zigzag effect to.
|
|
1734
|
+
* Can be a single side string or an array of sides.
|
|
1735
|
+
*/
|
|
1736
|
+
sides = input.required({ ...(ngDevMode ? { debugName: "sides" } : {}), alias: 'mglonZigzag' });
|
|
1737
|
+
/**
|
|
1738
|
+
* Size of the zigzag teeth (e.g., '10px', '0.5em').
|
|
1739
|
+
* Defaults to '5px' if not specified.
|
|
1740
|
+
*/
|
|
1741
|
+
zigzagSize = input('5px', ...(ngDevMode ? [{ debugName: "zigzagSize" }] : []));
|
|
1742
|
+
elementRef = inject(ElementRef);
|
|
1743
|
+
renderer = inject(Renderer2);
|
|
1744
|
+
constructor() {
|
|
1745
|
+
effect(() => {
|
|
1746
|
+
const sidesInput = this.sides();
|
|
1747
|
+
const sides = Array.isArray(sidesInput) ? sidesInput : [sidesInput];
|
|
1748
|
+
// Clear all existing zigzag classes first
|
|
1749
|
+
const allSides = ['top', 'right', 'bottom', 'left'];
|
|
1750
|
+
allSides.forEach(side => {
|
|
1751
|
+
this.renderer.removeClass(this.elementRef.nativeElement, `mglon-zigzag-${side}`);
|
|
1752
|
+
});
|
|
1753
|
+
// Apply classes for requested sides
|
|
1754
|
+
sides.forEach(side => {
|
|
1755
|
+
if (side && allSides.includes(side)) {
|
|
1756
|
+
this.renderer.addClass(this.elementRef.nativeElement, `mglon-zigzag-${side}`);
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZigzagDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1762
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: ZigzagDirective, isStandalone: true, selector: "[mglonZigzag]", inputs: { sides: { classPropertyName: "sides", publicName: "mglonZigzag", isSignal: true, isRequired: true, transformFunction: null }, zigzagSize: { classPropertyName: "zigzagSize", publicName: "zigzagSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--mglon-zigzag-size": "zigzagSize()" } }, ngImport: i0 });
|
|
1763
|
+
}
|
|
1764
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZigzagDirective, decorators: [{
|
|
1765
|
+
type: Directive,
|
|
1766
|
+
args: [{
|
|
1767
|
+
selector: '[mglonZigzag]',
|
|
1768
|
+
standalone: true,
|
|
1769
|
+
host: {
|
|
1770
|
+
'[style.--mglon-zigzag-size]': 'zigzagSize()'
|
|
1771
|
+
}
|
|
1772
|
+
}]
|
|
1773
|
+
}], ctorParameters: () => [], propDecorators: { sides: [{ type: i0.Input, args: [{ isSignal: true, alias: "mglonZigzag", required: true }] }], zigzagSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zigzagSize", required: false }] }] } });
|
|
1774
|
+
|
|
1775
|
+
class ResizableDirective {
|
|
1776
|
+
/**
|
|
1777
|
+
* Sides to allow resizing on.
|
|
1778
|
+
*/
|
|
1779
|
+
sides = input.required({ ...(ngDevMode ? { debugName: "sides" } : {}), alias: 'mglonResizable' });
|
|
1780
|
+
/**
|
|
1781
|
+
* Size of the resize handle area in pixels.
|
|
1782
|
+
*/
|
|
1783
|
+
handleSize = input(6, ...(ngDevMode ? [{ debugName: "handleSize" }] : []));
|
|
1784
|
+
/**
|
|
1785
|
+
* Emitted when a resize interaction starts (mousedown on handle).
|
|
1786
|
+
*/
|
|
1787
|
+
resizeStart = output();
|
|
1788
|
+
elementRef = inject(ElementRef);
|
|
1789
|
+
renderer = inject(Renderer2);
|
|
1790
|
+
store = inject(CalendarStore);
|
|
1791
|
+
destroyMouseDownListener;
|
|
1792
|
+
constructor() {
|
|
1793
|
+
effect(() => {
|
|
1794
|
+
const sidesInput = this.sides();
|
|
1795
|
+
const sides = Array.isArray(sidesInput) ? sidesInput : [sidesInput];
|
|
1796
|
+
const allSides = ['top', 'right', 'bottom', 'left'];
|
|
1797
|
+
allSides.forEach(side => {
|
|
1798
|
+
this.renderer.removeClass(this.elementRef.nativeElement, `mglon-resizable-${side}`);
|
|
1799
|
+
});
|
|
1800
|
+
sides.forEach(side => {
|
|
1801
|
+
if (side && allSides.includes(side)) {
|
|
1802
|
+
this.renderer.addClass(this.elementRef.nativeElement, `mglon-resizable-${side}`);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
onMouseEnter() {
|
|
1808
|
+
if (this.destroyMouseDownListener) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
this.destroyMouseDownListener = this.renderer.listen(this.elementRef.nativeElement, 'mousedown', (event) => this.handleMouseDown(event));
|
|
1812
|
+
}
|
|
1813
|
+
onMouseLeave() {
|
|
1814
|
+
if (this.destroyMouseDownListener) {
|
|
1815
|
+
this.destroyMouseDownListener();
|
|
1816
|
+
this.destroyMouseDownListener = undefined;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
ngOnDestroy() {
|
|
1820
|
+
this.cleanupListeners();
|
|
1821
|
+
}
|
|
1822
|
+
cleanupListeners() {
|
|
1823
|
+
if (this.destroyMouseDownListener) {
|
|
1824
|
+
this.destroyMouseDownListener();
|
|
1825
|
+
this.destroyMouseDownListener = undefined;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
handleMouseDown(event) {
|
|
1829
|
+
const sidesInput = this.sides();
|
|
1830
|
+
const activeSides = Array.isArray(sidesInput) ? sidesInput : [sidesInput];
|
|
1831
|
+
const rect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
1832
|
+
const x = event.clientX - rect.left;
|
|
1833
|
+
const y = event.clientY - rect.top;
|
|
1834
|
+
const w = rect.width;
|
|
1835
|
+
const h = rect.height;
|
|
1836
|
+
const handle = this.handleSize();
|
|
1837
|
+
let clickedSide = null;
|
|
1838
|
+
if (activeSides.includes('left') && x <= handle) {
|
|
1839
|
+
clickedSide = 'left';
|
|
1840
|
+
}
|
|
1841
|
+
else if (activeSides.includes('right') && x >= w - handle) {
|
|
1842
|
+
clickedSide = 'right';
|
|
1843
|
+
}
|
|
1844
|
+
else if (activeSides.includes('top') && y <= handle) {
|
|
1845
|
+
clickedSide = 'top';
|
|
1846
|
+
}
|
|
1847
|
+
else if (activeSides.includes('bottom') && y >= h - handle) {
|
|
1848
|
+
clickedSide = 'bottom';
|
|
1849
|
+
}
|
|
1850
|
+
if (clickedSide) {
|
|
1851
|
+
if (this.store.interactionMode() !== 'none' && this.store.interactionMode() !== 'dragging') {
|
|
1852
|
+
// Only allow resizing if nothing else is happening
|
|
1853
|
+
// Note: we might be in 'dragging' mode because of pointerdown already firing on MonthSlot
|
|
1854
|
+
// So we might need to override it.
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
event.preventDefault();
|
|
1858
|
+
event.stopPropagation();
|
|
1859
|
+
this.store.setInteractionMode('resizing');
|
|
1860
|
+
this.resizeStart.emit({ side: clickedSide, event });
|
|
1861
|
+
// Add global mouseup to reset interaction mode
|
|
1862
|
+
const onUp = () => {
|
|
1863
|
+
this.store.setInteractionMode('none');
|
|
1864
|
+
window.removeEventListener('mouseup', onUp);
|
|
1865
|
+
};
|
|
1866
|
+
window.addEventListener('mouseup', onUp);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResizableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1870
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: ResizableDirective, isStandalone: true, selector: "[mglonResizable]", inputs: { sides: { classPropertyName: "sides", publicName: "mglonResizable", isSignal: true, isRequired: true, transformFunction: null }, handleSize: { classPropertyName: "handleSize", publicName: "handleSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { resizeStart: "resizeStart" }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" }, properties: { "style.--mglon-resize-handle-size": "handleSize() + \"px\"" } }, ngImport: i0 });
|
|
1871
|
+
}
|
|
1872
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResizableDirective, decorators: [{
|
|
1873
|
+
type: Directive,
|
|
1874
|
+
args: [{
|
|
1875
|
+
selector: '[mglonResizable]',
|
|
1876
|
+
standalone: true,
|
|
1877
|
+
host: {
|
|
1878
|
+
'[style.--mglon-resize-handle-size]': 'handleSize() + "px"'
|
|
1879
|
+
}
|
|
1880
|
+
}]
|
|
1881
|
+
}], ctorParameters: () => [], propDecorators: { sides: [{ type: i0.Input, args: [{ isSignal: true, alias: "mglonResizable", required: true }] }], handleSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "handleSize", required: false }] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }], onMouseEnter: [{
|
|
1882
|
+
type: HostListener,
|
|
1883
|
+
args: ['mouseenter']
|
|
1884
|
+
}], onMouseLeave: [{
|
|
1885
|
+
type: HostListener,
|
|
1886
|
+
args: ['mouseleave']
|
|
1887
|
+
}] } });
|
|
1888
|
+
|
|
1889
|
+
class MonthRecurrenceDirective {
|
|
1890
|
+
el = inject(ElementRef);
|
|
1891
|
+
vcr = inject(ViewContainerRef);
|
|
1892
|
+
/** Whether the event is recurrent */
|
|
1893
|
+
isRecurrent = input(false, { ...(ngDevMode ? { debugName: "isRecurrent" } : {}), alias: 'mglonMonthRecurrence' });
|
|
1894
|
+
constructor() {
|
|
1895
|
+
effect(() => {
|
|
1896
|
+
if (this.isRecurrent()) {
|
|
1897
|
+
this.addIcon();
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
this.removeIcon();
|
|
1901
|
+
}
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
addIcon() {
|
|
1905
|
+
// Check if icon already exists to avoid duplicates
|
|
1906
|
+
if (this.el.nativeElement.querySelector('.mglon-month-slot__recurrence-icon')) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const componentRef = this.vcr.createComponent(IconComponent);
|
|
1910
|
+
componentRef.setInput('name', 'cycle');
|
|
1911
|
+
// Get the DOM element of the icon
|
|
1912
|
+
const iconElement = componentRef.location.nativeElement;
|
|
1913
|
+
iconElement.classList.add('mglon-month-slot__recurrence-icon');
|
|
1914
|
+
// Insert before the text content
|
|
1915
|
+
this.el.nativeElement.prepend(iconElement);
|
|
1916
|
+
}
|
|
1917
|
+
removeIcon() {
|
|
1918
|
+
const icon = this.el.nativeElement.querySelector('.mglon-month-slot__recurrence-icon');
|
|
1919
|
+
if (icon) {
|
|
1920
|
+
icon.remove();
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthRecurrenceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1924
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: MonthRecurrenceDirective, isStandalone: true, selector: "[mglonMonthRecurrence]", inputs: { isRecurrent: { classPropertyName: "isRecurrent", publicName: "mglonMonthRecurrence", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1925
|
+
}
|
|
1926
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthRecurrenceDirective, decorators: [{
|
|
1927
|
+
type: Directive,
|
|
1928
|
+
args: [{
|
|
1929
|
+
selector: '[mglonMonthRecurrence]',
|
|
1930
|
+
standalone: true
|
|
1931
|
+
}]
|
|
1932
|
+
}], ctorParameters: () => [], propDecorators: { isRecurrent: [{ type: i0.Input, args: [{ isSignal: true, alias: "mglonMonthRecurrence", required: false }] }] } });
|
|
1933
|
+
|
|
1934
|
+
class AllDayDirective {
|
|
1935
|
+
el = inject(ElementRef);
|
|
1936
|
+
vcr = inject(ViewContainerRef);
|
|
1937
|
+
/** Whether the event is all-day */
|
|
1938
|
+
isAllDay = input(false, { ...(ngDevMode ? { debugName: "isAllDay" } : {}), alias: 'mglonAllDay' });
|
|
1939
|
+
/** Color for the dot icon */
|
|
1940
|
+
dotColor = input('', ...(ngDevMode ? [{ debugName: "dotColor" }] : []));
|
|
1941
|
+
constructor() {
|
|
1942
|
+
effect(() => {
|
|
1943
|
+
if (this.isAllDay()) {
|
|
1944
|
+
this.addIcon();
|
|
1945
|
+
// The host binding in MonthSlot will handle most styling,
|
|
1946
|
+
// but we add a class for extra specificity if needed.
|
|
1947
|
+
this.el.nativeElement.classList.add('mglon-event--all-day');
|
|
1948
|
+
}
|
|
1949
|
+
else {
|
|
1950
|
+
this.removeIcon();
|
|
1951
|
+
this.el.nativeElement.classList.remove('mglon-event--all-day');
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
// Handle dynamic color changes for the dot
|
|
1955
|
+
effect(() => {
|
|
1956
|
+
const color = this.dotColor();
|
|
1957
|
+
const icon = this.el.nativeElement.querySelector('.mglon-event__all-day-icon');
|
|
1958
|
+
if (icon && color) {
|
|
1959
|
+
icon.style.color = color;
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
addIcon() {
|
|
1964
|
+
if (this.el.nativeElement.querySelector('.mglon-event__all-day-icon')) {
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
const componentRef = this.vcr.createComponent(IconComponent);
|
|
1968
|
+
componentRef.setInput('name', 'dot');
|
|
1969
|
+
const iconElement = componentRef.location.nativeElement;
|
|
1970
|
+
iconElement.classList.add('mglon-event__all-day-icon');
|
|
1971
|
+
if (this.dotColor()) {
|
|
1972
|
+
iconElement.style.color = this.dotColor();
|
|
1973
|
+
}
|
|
1974
|
+
// Insert before the title/text
|
|
1975
|
+
this.el.nativeElement.prepend(iconElement);
|
|
1976
|
+
}
|
|
1977
|
+
removeIcon() {
|
|
1978
|
+
const icon = this.el.nativeElement.querySelector('.mglon-event__all-day-icon');
|
|
1979
|
+
if (icon) {
|
|
1980
|
+
icon.remove();
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AllDayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1984
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: AllDayDirective, isStandalone: true, selector: "[mglonAllDay]", inputs: { isAllDay: { classPropertyName: "isAllDay", publicName: "mglonAllDay", isSignal: true, isRequired: false, transformFunction: null }, dotColor: { classPropertyName: "dotColor", publicName: "dotColor", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1985
|
+
}
|
|
1986
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AllDayDirective, decorators: [{
|
|
1987
|
+
type: Directive,
|
|
1988
|
+
args: [{
|
|
1989
|
+
selector: '[mglonAllDay]',
|
|
1990
|
+
standalone: true
|
|
1991
|
+
}]
|
|
1992
|
+
}], ctorParameters: () => [], propDecorators: { isAllDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "mglonAllDay", required: false }] }], dotColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotColor", required: false }] }] } });
|
|
1993
|
+
|
|
1994
|
+
/** Maps EventSlotRadius to CSS variable names */
|
|
1995
|
+
const RADIUS_VAR_MAP = {
|
|
1996
|
+
'none': '0',
|
|
1997
|
+
'sm': 'var(--mglon-schedule-radius-sm)', // Small fixed value for subtle rounding on slots
|
|
1998
|
+
'full': 'var(--mglon-schedule-radius-full)'
|
|
1999
|
+
};
|
|
2000
|
+
class MonthSlot {
|
|
2001
|
+
store = inject(CalendarStore);
|
|
2002
|
+
slot = input.required(...(ngDevMode ? [{ debugName: "slot" }] : []));
|
|
2003
|
+
/** Whether this specific event is being dragged */
|
|
2004
|
+
isDragging = computed(() => this.store.dragState().eventId === this.slot().idEvent, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
2005
|
+
/** Whether this event is currently hovered globally */
|
|
2006
|
+
isHovered = computed(() => this.store.hoveredEventId() === this.slot().idEvent, ...(ngDevMode ? [{ debugName: "isHovered" }] : []));
|
|
2007
|
+
/**
|
|
2008
|
+
* Gets the event data from the store using the slot's event ID.
|
|
2009
|
+
*/
|
|
2010
|
+
event = computed(() => {
|
|
2011
|
+
return this.store.getEvent(this.slot().idEvent);
|
|
2012
|
+
}, ...(ngDevMode ? [{ debugName: "event" }] : []));
|
|
2013
|
+
/** Whether this event is part of a recurrence series */
|
|
2014
|
+
isRecurrent = computed(() => {
|
|
2015
|
+
const e = this.event();
|
|
2016
|
+
return e?.type === 'event' && e.isRecurrenceInstance === true;
|
|
2017
|
+
}, ...(ngDevMode ? [{ debugName: "isRecurrent" }] : []));
|
|
2018
|
+
/** Whether the event is all-day */
|
|
2019
|
+
isAllDay = computed(() => {
|
|
2020
|
+
return this.event()?.isAllDay === true;
|
|
2021
|
+
}, ...(ngDevMode ? [{ debugName: "isAllDay" }] : []));
|
|
2022
|
+
/**
|
|
2023
|
+
* Display title for the slot.
|
|
2024
|
+
*/
|
|
2025
|
+
title = computed(() => {
|
|
2026
|
+
return this.event()?.title ?? '';
|
|
2027
|
+
}, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
2028
|
+
/**
|
|
2029
|
+
* Complete color scheme calculation based on event type
|
|
2030
|
+
*/
|
|
2031
|
+
colorScheme = computed(() => {
|
|
2032
|
+
// 1. Resolve base raw color
|
|
2033
|
+
const rawColor = this.rawColor();
|
|
2034
|
+
// 2. Generate all adaptive variants
|
|
2035
|
+
const scheme = generateAdaptiveColorScheme(rawColor);
|
|
2036
|
+
// 3. Select variant based on configuration and event type
|
|
2037
|
+
const useDynamic = this.store.uiConfig().grid.useDynamicColors;
|
|
2038
|
+
if (!useDynamic) {
|
|
2039
|
+
return scheme.raw;
|
|
2040
|
+
}
|
|
2041
|
+
return this.isRecurrent() ? scheme.pastel : scheme.vivid;
|
|
2042
|
+
}, ...(ngDevMode ? [{ debugName: "colorScheme" }] : []));
|
|
2043
|
+
/**
|
|
2044
|
+
* Raw color resolved for the event (before adaptive variants)
|
|
2045
|
+
*/
|
|
2046
|
+
rawColor = computed(() => {
|
|
2047
|
+
return getEventColor({ color: this.slot().color, resourceId: this.event()?.resourceId }, (id) => this.store.getResource(id), this.store.uiConfig().grid.eventSlots.color || '#1a73e8');
|
|
2048
|
+
}, ...(ngDevMode ? [{ debugName: "rawColor" }] : []));
|
|
2049
|
+
/**
|
|
2050
|
+
* Border radius from uiConfig, mapped to CSS variable.
|
|
2051
|
+
*/
|
|
2052
|
+
slotRadius = computed(() => {
|
|
2053
|
+
const rounded = this.store.uiConfig().grid.eventSlots.rounded;
|
|
2054
|
+
return RADIUS_VAR_MAP[rounded];
|
|
2055
|
+
}, ...(ngDevMode ? [{ debugName: "slotRadius" }] : []));
|
|
2056
|
+
/**
|
|
2057
|
+
* Determines which sides get zigzag effect based on slot type.
|
|
2058
|
+
* - 'first': right side (continues to next week)
|
|
2059
|
+
* - 'last': left side (comes from previous week)
|
|
2060
|
+
* - 'middle': both sides (spans entire week)
|
|
2061
|
+
* - 'full': no zigzag (complete within week)
|
|
2062
|
+
*/
|
|
2063
|
+
zigzagSides = computed(() => {
|
|
2064
|
+
const type = this.slot().type;
|
|
2065
|
+
switch (type) {
|
|
2066
|
+
case 'first':
|
|
2067
|
+
return ['right'];
|
|
2068
|
+
case 'last':
|
|
2069
|
+
return ['left'];
|
|
2070
|
+
case 'middle':
|
|
2071
|
+
return ['left', 'right'];
|
|
2072
|
+
default:
|
|
2073
|
+
return [];
|
|
2074
|
+
}
|
|
2075
|
+
}, ...(ngDevMode ? [{ debugName: "zigzagSides" }] : []));
|
|
2076
|
+
/**
|
|
2077
|
+
* Determines which sides are resizable.
|
|
2078
|
+
* Only sides without zigzag are resizable, and only if resizableEvents is enabled.
|
|
2079
|
+
*/
|
|
2080
|
+
resizableSides = computed(() => {
|
|
2081
|
+
// Check global config from store
|
|
2082
|
+
if (!this.store.config().resizableEvents) {
|
|
2083
|
+
return [];
|
|
2084
|
+
}
|
|
2085
|
+
// Check resource-specific config if available
|
|
2086
|
+
const event = this.event();
|
|
2087
|
+
if (event?.resourceId) {
|
|
2088
|
+
const resource = this.store.getResource(event.resourceId);
|
|
2089
|
+
if (resource && resource.resizableEvents === false) {
|
|
2090
|
+
return [];
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const type = this.slot().type;
|
|
2094
|
+
switch (type) {
|
|
2095
|
+
case 'first':
|
|
2096
|
+
return ['left'];
|
|
2097
|
+
case 'last':
|
|
2098
|
+
return ['right'];
|
|
2099
|
+
case 'middle':
|
|
2100
|
+
return [];
|
|
2101
|
+
case 'full':
|
|
2102
|
+
return ['left', 'right'];
|
|
2103
|
+
default:
|
|
2104
|
+
return [];
|
|
2105
|
+
}
|
|
2106
|
+
}, ...(ngDevMode ? [{ debugName: "resizableSides" }] : []));
|
|
2107
|
+
dragDelayTimer;
|
|
2108
|
+
DRAG_DELAY = 150; // ms
|
|
2109
|
+
DRAG_THRESHOLD = 5; // px
|
|
2110
|
+
startPointerPos = { x: 0, y: 0 };
|
|
2111
|
+
clickTimer;
|
|
2112
|
+
ignoreNextClick = false;
|
|
2113
|
+
onPointerDown(event) {
|
|
2114
|
+
// Only handle primary button and if no other interaction is active
|
|
2115
|
+
if (event.button !== 0 || this.store.interactionMode() !== 'none')
|
|
2116
|
+
return;
|
|
2117
|
+
// Check if we clicked a resize handle
|
|
2118
|
+
const target = event.currentTarget;
|
|
2119
|
+
const rect = target.getBoundingClientRect();
|
|
2120
|
+
const x = event.clientX - rect.left;
|
|
2121
|
+
const sides = this.resizableSides();
|
|
2122
|
+
const handleSize = 6; // Matching ResizableDirective default
|
|
2123
|
+
if (sides.includes('left') && x <= handleSize)
|
|
2124
|
+
return;
|
|
2125
|
+
if (sides.includes('right') && x >= rect.width - handleSize)
|
|
2126
|
+
return;
|
|
2127
|
+
// Stop propagation immediately to prevent background selection from seeing this event
|
|
2128
|
+
event.stopPropagation();
|
|
2129
|
+
this.startPointerPos = { x: event.clientX, y: event.clientY };
|
|
2130
|
+
// Lock the mutex immediately to block other potential listeners (like Selection)
|
|
2131
|
+
this.store.setInteractionMode('dragging');
|
|
2132
|
+
this.dragDelayTimer = setTimeout(() => {
|
|
2133
|
+
this.initiateDrag(event);
|
|
2134
|
+
}, this.DRAG_DELAY);
|
|
2135
|
+
// Bind movement and release to handle cancellation
|
|
2136
|
+
const onMove = (e) => this.onGlobalMove(e);
|
|
2137
|
+
const onUp = (e) => {
|
|
2138
|
+
this.onGlobalUp(e);
|
|
2139
|
+
window.removeEventListener('pointermove', onMove);
|
|
2140
|
+
window.removeEventListener('pointerup', onUp);
|
|
2141
|
+
window.removeEventListener('pointercancel', onUp);
|
|
2142
|
+
};
|
|
2143
|
+
window.addEventListener('pointermove', onMove);
|
|
2144
|
+
window.addEventListener('pointerup', onUp);
|
|
2145
|
+
window.addEventListener('pointercancel', onUp);
|
|
2146
|
+
}
|
|
2147
|
+
initiateDrag(event) {
|
|
2148
|
+
const target = event.currentTarget || event.target;
|
|
2149
|
+
if (target.setPointerCapture) {
|
|
2150
|
+
target.setPointerCapture(event.pointerId);
|
|
2151
|
+
}
|
|
2152
|
+
// Calculate grabDate (exact day clicked)
|
|
2153
|
+
const rect = target.getBoundingClientRect();
|
|
2154
|
+
const clickX = this.startPointerPos.x - rect.left;
|
|
2155
|
+
const slotWidth = rect.width;
|
|
2156
|
+
const daysSpan = differenceInCalendarDays(this.slot().end, this.slot().start) + 1;
|
|
2157
|
+
const dayOffset = Math.max(0, Math.min(Math.floor((clickX / slotWidth) * daysSpan), daysSpan - 1));
|
|
2158
|
+
const grabDate = addDays(this.slot().start, dayOffset);
|
|
2159
|
+
this.store.setDragStart(this.slot().idEvent, grabDate);
|
|
2160
|
+
this.store.setInteractionMode('dragging');
|
|
2161
|
+
// Dispatch dragStart interaction
|
|
2162
|
+
this.store.dispatchInteraction('dragStart', this.slot().idEvent, {
|
|
2163
|
+
event: this.event(),
|
|
2164
|
+
slotId: this.slot().id,
|
|
2165
|
+
data: {
|
|
2166
|
+
grabDate,
|
|
2167
|
+
hoverDate: null
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
onGlobalMove(event) {
|
|
2172
|
+
if (this.store.dragState().eventId) { // Check if we are actually dragging (timer finished)
|
|
2173
|
+
this.onPointerMove(event);
|
|
2174
|
+
}
|
|
2175
|
+
else if (this.dragDelayTimer) {
|
|
2176
|
+
// Check if we moved too much before the delay finished
|
|
2177
|
+
const dist = Math.sqrt(Math.pow(event.clientX - this.startPointerPos.x, 2) +
|
|
2178
|
+
Math.pow(event.clientY - this.startPointerPos.y, 2));
|
|
2179
|
+
if (dist > this.DRAG_THRESHOLD) {
|
|
2180
|
+
this.store.setInteractionMode('none'); // Release mutex if it was a scroll/swipe
|
|
2181
|
+
this.cancelDragDelay();
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
onGlobalUp(event) {
|
|
2186
|
+
this.cancelDragDelay();
|
|
2187
|
+
if (this.store.dragState().eventId) {
|
|
2188
|
+
this.onPointerUp(event);
|
|
2189
|
+
// Occlude the next click event that follows pointerup
|
|
2190
|
+
this.ignoreNextClick = true;
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
// It was just a click or a cancelled drag, release mutex
|
|
2194
|
+
this.store.setInteractionMode('none');
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
cancelDragDelay() {
|
|
2198
|
+
if (this.dragDelayTimer) {
|
|
2199
|
+
clearTimeout(this.dragDelayTimer);
|
|
2200
|
+
this.dragDelayTimer = undefined;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
onPointerMove(event) {
|
|
2204
|
+
if (!this.isDragging())
|
|
2205
|
+
return;
|
|
2206
|
+
// Detect which cell we are over using coordinates
|
|
2207
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
2208
|
+
const cell = element?.closest('.mglon-month-cell');
|
|
2209
|
+
if (cell) {
|
|
2210
|
+
const timestamp = cell.getAttribute('data-mglon-date');
|
|
2211
|
+
if (timestamp) {
|
|
2212
|
+
const hoverDate = new Date(parseInt(timestamp, 10));
|
|
2213
|
+
this.store.setDragHover(hoverDate);
|
|
2214
|
+
this.store.updateDraggedEventPosition();
|
|
2215
|
+
// Dispatch drag interaction event
|
|
2216
|
+
this.store.dispatchInteraction('drag', this.slot().idEvent, {
|
|
2217
|
+
event: this.event(),
|
|
2218
|
+
slotId: this.slot().id,
|
|
2219
|
+
data: {
|
|
2220
|
+
grabDate: this.store.dragState().grabDate,
|
|
2221
|
+
hoverDate: hoverDate
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
onPointerUp(event) {
|
|
2228
|
+
// Dispatch dragEnd interaction before clearing state
|
|
2229
|
+
const dragState = this.store.dragState();
|
|
2230
|
+
this.store.dispatchInteraction('dragEnd', this.slot().idEvent, {
|
|
2231
|
+
event: this.event(),
|
|
2232
|
+
slotId: this.slot().id,
|
|
2233
|
+
data: {
|
|
2234
|
+
grabDate: dragState.grabDate,
|
|
2235
|
+
hoverDate: dragState.hoverDate
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
this.store.setInteractionMode('none');
|
|
2239
|
+
this.store.clearDragState();
|
|
2240
|
+
}
|
|
2241
|
+
onResizeStart(event) {
|
|
2242
|
+
this.store.setResizeStart(this.slot().idEvent, event.side);
|
|
2243
|
+
// Dispatch resizeStart interaction
|
|
2244
|
+
this.store.dispatchInteraction('resizeStart', this.slot().idEvent, {
|
|
2245
|
+
event: this.event(),
|
|
2246
|
+
slotId: this.slot().id,
|
|
2247
|
+
data: {
|
|
2248
|
+
side: event.side,
|
|
2249
|
+
date: this.slot().start // Basic reference date for start
|
|
2250
|
+
}
|
|
2251
|
+
});
|
|
2252
|
+
// Bind movement and release for real-time resize feedback
|
|
2253
|
+
// Similar to drag-and-drop, we use global listeners
|
|
2254
|
+
const onMove = (e) => this.onGlobalResizeMove(e);
|
|
2255
|
+
const onUp = () => {
|
|
2256
|
+
// Occlude the next click event
|
|
2257
|
+
this.ignoreNextClick = true;
|
|
2258
|
+
// Dispatch resizeEnd interaction before clearing state
|
|
2259
|
+
const resizeState = this.store.resizeState();
|
|
2260
|
+
this.store.dispatchInteraction('resizeEnd', this.slot().idEvent, {
|
|
2261
|
+
event: this.event(),
|
|
2262
|
+
slotId: this.slot().id,
|
|
2263
|
+
data: {
|
|
2264
|
+
side: resizeState.side,
|
|
2265
|
+
date: resizeState.hoverDate || (resizeState.side === 'left' ? this.slot().start : this.slot().end)
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
this.store.clearResizeState();
|
|
2269
|
+
this.store.setInteractionMode('none');
|
|
2270
|
+
window.removeEventListener('pointermove', onMove);
|
|
2271
|
+
window.removeEventListener('pointerup', onUp);
|
|
2272
|
+
window.removeEventListener('pointercancel', onUp);
|
|
2273
|
+
};
|
|
2274
|
+
window.addEventListener('pointermove', onMove);
|
|
2275
|
+
window.addEventListener('pointerup', onUp);
|
|
2276
|
+
window.addEventListener('pointercancel', onUp);
|
|
2277
|
+
}
|
|
2278
|
+
onGlobalResizeMove(event) {
|
|
2279
|
+
if (this.store.interactionMode() !== 'resizing')
|
|
2280
|
+
return;
|
|
2281
|
+
// Detect which cell we are over using coordinates
|
|
2282
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
2283
|
+
const cell = element?.closest('.mglon-month-cell');
|
|
2284
|
+
if (cell) {
|
|
2285
|
+
const timestamp = cell.getAttribute('data-mglon-date');
|
|
2286
|
+
if (timestamp) {
|
|
2287
|
+
const hoverDate = new Date(parseInt(timestamp, 10));
|
|
2288
|
+
this.store.setResizeHover(hoverDate);
|
|
2289
|
+
this.store.updateResizedEvent();
|
|
2290
|
+
// Dispatch resize interaction event
|
|
2291
|
+
this.store.dispatchInteraction('resize', this.slot().idEvent, {
|
|
2292
|
+
event: this.event(),
|
|
2293
|
+
slotId: this.slot().id,
|
|
2294
|
+
data: {
|
|
2295
|
+
side: this.store.resizeState().side,
|
|
2296
|
+
date: hoverDate
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
onMouseEnter() {
|
|
2303
|
+
if (this.store.interactionMode() !== 'none')
|
|
2304
|
+
return;
|
|
2305
|
+
this.store.setHoveredEvent(this.slot().idEvent);
|
|
2306
|
+
this.store.dispatchInteraction('mouseenter', this.slot().idEvent, {
|
|
2307
|
+
event: this.event(),
|
|
2308
|
+
slotId: this.slot().id
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
onMouseLeave() {
|
|
2312
|
+
if (this.store.interactionMode() !== 'none')
|
|
2313
|
+
return;
|
|
2314
|
+
this.store.setHoveredEvent(null);
|
|
2315
|
+
this.store.dispatchInteraction('mouseleave', this.slot().idEvent, {
|
|
2316
|
+
event: this.event(),
|
|
2317
|
+
slotId: this.slot().id
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
onClick(event) {
|
|
2321
|
+
event.stopPropagation();
|
|
2322
|
+
// Prevent click if we just finished a drag or resize
|
|
2323
|
+
if (this.ignoreNextClick) {
|
|
2324
|
+
this.ignoreNextClick = false;
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
// Double click detection: if another click arrives within 250ms, cancel this one
|
|
2328
|
+
if (this.clickTimer) {
|
|
2329
|
+
clearTimeout(this.clickTimer);
|
|
2330
|
+
this.clickTimer = undefined;
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
this.clickTimer = setTimeout(() => {
|
|
2334
|
+
this.clickTimer = undefined;
|
|
2335
|
+
this.store.dispatchInteraction('click', this.slot().idEvent, {
|
|
2336
|
+
event: this.event(),
|
|
2337
|
+
slotId: this.slot().id,
|
|
2338
|
+
originalEvent: event
|
|
2339
|
+
});
|
|
2340
|
+
}, 250);
|
|
2341
|
+
}
|
|
2342
|
+
onDblClick(event) {
|
|
2343
|
+
event.stopPropagation();
|
|
2344
|
+
if (this.clickTimer) {
|
|
2345
|
+
clearTimeout(this.clickTimer);
|
|
2346
|
+
this.clickTimer = undefined;
|
|
2347
|
+
}
|
|
2348
|
+
this.store.dispatchInteraction('dblclick', this.slot().idEvent, {
|
|
2349
|
+
event: this.event(),
|
|
2350
|
+
slotId: this.slot().id,
|
|
2351
|
+
originalEvent: event
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
onContextMenu(event) {
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
event.stopPropagation();
|
|
2357
|
+
this.store.dispatchInteraction('contextmenu', this.slot().idEvent, {
|
|
2358
|
+
event: this.event(),
|
|
2359
|
+
slotId: this.slot().id,
|
|
2360
|
+
originalEvent: event
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthSlot, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2364
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: MonthSlot, isStandalone: true, selector: "mglon-month-slot", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style.position": "\"absolute\"", "style.top.px": "slot().position.top", "style.left.%": "slot().position.left", "style.width.%": "slot().position.width", "style.--slot-width.%": "slot().position.width", "style.height.px": "slot().position.height", "style.z-index": "slot().zIndex", "style.--slot-bg": "isAllDay() ? \"var(--mglon-all-day-bg, var(--mglon-schedule-neutral-200))\" : colorScheme().base", "style.--slot-hover": "isAllDay() ? \"var(--mglon-all-day-hover, var(--mglon-schedule-neutral-300))\" : colorScheme().hover", "style.--slot-text": "isAllDay() ? \"var(--mglon-all-day-text, var(--mglon-schedule-on-surface))\" : colorScheme().text", "style.--slot-text-hover": "isAllDay() ? \"var(--mglon-all-day-text-hover, var(--mglon-schedule-on-surface))\" : colorScheme().textHover", "style.--slot-radius": "slotRadius()", "attr.data-slot-type": "slot().type", "class.mglon-month-slot--first": "slot().type === \"first\"", "class.mglon-month-slot--last": "slot().type === \"last\"", "class.mglon-month-slot--middle": "slot().type === \"middle\"", "class.mglon-month-slot--full": "slot().type === \"full\"", "class.mglon-event--all-day": "isAllDay()", "class.mglon-month-slot--dragging": "isDragging()", "class.mglon-month-slot--idle": "!isDragging()", "class.mglon-month-slot--hovered": "isHovered()" } }, ngImport: i0, template: "<!-- Renderizado de slots recurrentes (Se eliminan los eventos de resize y drag) -->\n@if(isRecurrent()) {\n<div class=\"mglon-month-slot__inner\" [mglonZigzag]=\"zigzagSides()\" (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\" (click)=\"onClick($event)\" (dblclick)=\"onDblClick($event)\"\n (contextmenu)=\"onContextMenu($event)\">\n <span class=\"mglon-month-slot__title\" [mglonMonthRecurrence]=\"isRecurrent()\" [mglonAllDay]=\"isAllDay()\"\n [dotColor]=\"rawColor()\">{{ title() }}</span>\n</div>\n}\n\n<!-- Renderizado de slots no recurrentes -->\n@else {\n<div class=\"mglon-month-slot__inner\" [mglonZigzag]=\"zigzagSides()\" [mglonResizable]=\"resizableSides()\"\n (resizeStart)=\"onResizeStart($event)\" (pointerdown)=\"onPointerDown($event)\" (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\" (click)=\"onClick($event)\" (dblclick)=\"onDblClick($event)\"\n (contextmenu)=\"onContextMenu($event)\">\n <span class=\"mglon-month-slot__title\" [mglonAllDay]=\"isAllDay()\" [dotColor]=\"rawColor()\">{{ title() }}</span>\n</div>\n}", styles: [".mglon-zigzag-right{position:relative}.mglon-zigzag-right:after{content:\"\";position:absolute;height:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;top:0;bottom:0;right:0;width:var(--mglon-zigzag-size, 5px);height:auto;transform:rotate(0)}.mglon-zigzag-left{position:relative}.mglon-zigzag-left:before{content:\"\";position:absolute;height:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;top:0;bottom:0;left:0;width:var(--mglon-zigzag-size, 5px);height:auto;transform:rotate(180deg)}.mglon-zigzag-top{position:relative}.mglon-zigzag-top:before{content:\"\";position:absolute;width:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;left:0;right:0;top:0;width:auto;height:var(--mglon-zigzag-size, 5px);transform:rotate(-90deg)}.mglon-zigzag-bottom{position:relative}.mglon-zigzag-bottom:after{content:\"\";position:absolute;width:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;left:0;right:0;bottom:0;width:auto;height:var(--mglon-zigzag-size, 5px);transform:rotate(90deg)}.mglon-resizable-left:before{content:\"\";position:absolute;background-color:transparent;z-index:10;top:0;bottom:0;left:0;width:var(--mglon-resize-handle-size, 6px);cursor:col-resize}.mglon-resizable-right:after{content:\"\";position:absolute;background-color:transparent;z-index:10;top:0;bottom:0;right:0;width:var(--mglon-resize-handle-size, 6px);cursor:col-resize}.mglon-resizable-top:before{content:\"\";position:absolute;background-color:transparent;z-index:10;left:0;right:0;top:0;height:var(--mglon-resize-handle-size, 6px);cursor:row-resize}.mglon-resizable-bottom:after{content:\"\";position:absolute;background-color:transparent;z-index:10;left:0;right:0;bottom:0;height:var(--mglon-resize-handle-size, 6px);cursor:row-resize}:host{display:flex;align-items:center;box-sizing:border-box;border-radius:var(--slot-radius, var(--mglon-schedule-radius-sm));background-color:var(--slot-bg, #4285f4);color:var(--slot-text, #fff);font-size:12px;line-height:1;overflow:hidden;cursor:grab;transition:background-color .15s ease;touch-action:none}:host:active{cursor:grabbing}:host:hover{background-color:var(--slot-hover);color:var(--slot-text-hover, var(--slot-text))}:host{pointer-events:all}:host(.mglon-month-slot--full){width:calc(var(--slot-width, 100%) - 4px)!important}:host(.mglon-month-slot--first){border-top-right-radius:0;border-bottom-right-radius:0}:host(.mglon-month-slot--last){border-top-left-radius:0;border-bottom-left-radius:0;width:calc(var(--slot-width, 100%) - 4px)!important}:host(.mglon-month-slot--middle){border-radius:0}.mglon-month-slot__inner{display:flex;align-items:center;width:100%;height:100%;padding:0 8px;box-sizing:border-box}.mglon-month-slot__title{display:inline-flex;align-items:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.mglon-month-slot__title .mglon-month-slot__recurrence-icon{flex-shrink:0;margin-right:var(--mglon-schedule-gap-sm, 8px);font-size:1.2em}:host(.mglon-month-slot--idle){cursor:grab;pointer-events:all}:host(.mglon-month-slot--dragging){opacity:.6;cursor:grabbing;box-shadow:var(--mglon-schedule-shadow-lg, 0 10px 25px rgba(0, 0, 0, .15));transform:scale(1.02);z-index:100!important;pointer-events:none}:host(.mglon-month-slot--hovered){background-color:var(--slot-hover);color:var(--slot-text-hover, var(--slot-text))}:host(.mglon-event--all-day){box-shadow:none;border:var(--slot-border, none)}:host(.mglon-event--all-day):hover{border:var(--slot-border, none)}.mglon-event__all-day-icon{flex-shrink:0;margin-right:var(--mglon-schedule-gap-sm, 8px);font-size:1.2em;display:flex!important;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "directive", type: ZigzagDirective, selector: "[mglonZigzag]", inputs: ["mglonZigzag", "zigzagSize"] }, { kind: "directive", type: ResizableDirective, selector: "[mglonResizable]", inputs: ["mglonResizable", "handleSize"], outputs: ["resizeStart"] }, { kind: "directive", type: MonthRecurrenceDirective, selector: "[mglonMonthRecurrence]", inputs: ["mglonMonthRecurrence"] }, { kind: "directive", type: AllDayDirective, selector: "[mglonAllDay]", inputs: ["mglonAllDay", "dotColor"] }] });
|
|
2365
|
+
}
|
|
2366
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthSlot, decorators: [{
|
|
2367
|
+
type: Component,
|
|
2368
|
+
args: [{ selector: 'mglon-month-slot', imports: [ZigzagDirective, ResizableDirective, MonthRecurrenceDirective, AllDayDirective], host: {
|
|
2369
|
+
'[style.position]': '"absolute"',
|
|
2370
|
+
'[style.top.px]': 'slot().position.top',
|
|
2371
|
+
'[style.left.%]': 'slot().position.left',
|
|
2372
|
+
'[style.width.%]': 'slot().position.width',
|
|
2373
|
+
'[style.--slot-width.%]': 'slot().position.width',
|
|
2374
|
+
'[style.height.px]': 'slot().position.height',
|
|
2375
|
+
'[style.z-index]': 'slot().zIndex',
|
|
2376
|
+
'[style.--slot-bg]': 'isAllDay() ? "var(--mglon-all-day-bg, var(--mglon-schedule-neutral-200))" : colorScheme().base',
|
|
2377
|
+
'[style.--slot-hover]': 'isAllDay() ? "var(--mglon-all-day-hover, var(--mglon-schedule-neutral-300))" : colorScheme().hover',
|
|
2378
|
+
'[style.--slot-text]': 'isAllDay() ? "var(--mglon-all-day-text, var(--mglon-schedule-on-surface))" : colorScheme().text',
|
|
2379
|
+
'[style.--slot-text-hover]': 'isAllDay() ? "var(--mglon-all-day-text-hover, var(--mglon-schedule-on-surface))" : colorScheme().textHover',
|
|
2380
|
+
'[style.--slot-radius]': 'slotRadius()',
|
|
2381
|
+
'[attr.data-slot-type]': 'slot().type',
|
|
2382
|
+
'[class.mglon-month-slot--first]': 'slot().type === "first"',
|
|
2383
|
+
'[class.mglon-month-slot--last]': 'slot().type === "last"',
|
|
2384
|
+
'[class.mglon-month-slot--middle]': 'slot().type === "middle"',
|
|
2385
|
+
'[class.mglon-month-slot--full]': 'slot().type === "full"',
|
|
2386
|
+
'[class.mglon-event--all-day]': 'isAllDay()',
|
|
2387
|
+
'[class.mglon-month-slot--dragging]': 'isDragging()',
|
|
2388
|
+
'[class.mglon-month-slot--idle]': '!isDragging()',
|
|
2389
|
+
'[class.mglon-month-slot--hovered]': 'isHovered()',
|
|
2390
|
+
}, template: "<!-- Renderizado de slots recurrentes (Se eliminan los eventos de resize y drag) -->\n@if(isRecurrent()) {\n<div class=\"mglon-month-slot__inner\" [mglonZigzag]=\"zigzagSides()\" (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\" (click)=\"onClick($event)\" (dblclick)=\"onDblClick($event)\"\n (contextmenu)=\"onContextMenu($event)\">\n <span class=\"mglon-month-slot__title\" [mglonMonthRecurrence]=\"isRecurrent()\" [mglonAllDay]=\"isAllDay()\"\n [dotColor]=\"rawColor()\">{{ title() }}</span>\n</div>\n}\n\n<!-- Renderizado de slots no recurrentes -->\n@else {\n<div class=\"mglon-month-slot__inner\" [mglonZigzag]=\"zigzagSides()\" [mglonResizable]=\"resizableSides()\"\n (resizeStart)=\"onResizeStart($event)\" (pointerdown)=\"onPointerDown($event)\" (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\" (click)=\"onClick($event)\" (dblclick)=\"onDblClick($event)\"\n (contextmenu)=\"onContextMenu($event)\">\n <span class=\"mglon-month-slot__title\" [mglonAllDay]=\"isAllDay()\" [dotColor]=\"rawColor()\">{{ title() }}</span>\n</div>\n}", styles: [".mglon-zigzag-right{position:relative}.mglon-zigzag-right:after{content:\"\";position:absolute;height:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;top:0;bottom:0;right:0;width:var(--mglon-zigzag-size, 5px);height:auto;transform:rotate(0)}.mglon-zigzag-left{position:relative}.mglon-zigzag-left:before{content:\"\";position:absolute;height:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;top:0;bottom:0;left:0;width:var(--mglon-zigzag-size, 5px);height:auto;transform:rotate(180deg)}.mglon-zigzag-top{position:relative}.mglon-zigzag-top:before{content:\"\";position:absolute;width:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;left:0;right:0;top:0;width:auto;height:var(--mglon-zigzag-size, 5px);transform:rotate(-90deg)}.mglon-zigzag-bottom{position:relative}.mglon-zigzag-bottom:after{content:\"\";position:absolute;width:var(--mglon-zigzag-size, 5px);background-color:var(--mglon-zigzag-color, white);z-index:1;--mglon-zigzag-mask-size: calc(var(--mglon-zigzag-size, 5px) * 1.6);--zigzag-mask: linear-gradient(225deg, #000 50%, transparent 0), linear-gradient(315deg, #000 50%, transparent 0);-webkit-mask:var(--zigzag-mask);mask:var(--zigzag-mask);-webkit-mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);mask-size:var(--mglon-zigzag-mask-size) var(--mglon-zigzag-mask-size);-webkit-mask-composite:source-over;mask-composite:add;left:0;right:0;bottom:0;width:auto;height:var(--mglon-zigzag-size, 5px);transform:rotate(90deg)}.mglon-resizable-left:before{content:\"\";position:absolute;background-color:transparent;z-index:10;top:0;bottom:0;left:0;width:var(--mglon-resize-handle-size, 6px);cursor:col-resize}.mglon-resizable-right:after{content:\"\";position:absolute;background-color:transparent;z-index:10;top:0;bottom:0;right:0;width:var(--mglon-resize-handle-size, 6px);cursor:col-resize}.mglon-resizable-top:before{content:\"\";position:absolute;background-color:transparent;z-index:10;left:0;right:0;top:0;height:var(--mglon-resize-handle-size, 6px);cursor:row-resize}.mglon-resizable-bottom:after{content:\"\";position:absolute;background-color:transparent;z-index:10;left:0;right:0;bottom:0;height:var(--mglon-resize-handle-size, 6px);cursor:row-resize}:host{display:flex;align-items:center;box-sizing:border-box;border-radius:var(--slot-radius, var(--mglon-schedule-radius-sm));background-color:var(--slot-bg, #4285f4);color:var(--slot-text, #fff);font-size:12px;line-height:1;overflow:hidden;cursor:grab;transition:background-color .15s ease;touch-action:none}:host:active{cursor:grabbing}:host:hover{background-color:var(--slot-hover);color:var(--slot-text-hover, var(--slot-text))}:host{pointer-events:all}:host(.mglon-month-slot--full){width:calc(var(--slot-width, 100%) - 4px)!important}:host(.mglon-month-slot--first){border-top-right-radius:0;border-bottom-right-radius:0}:host(.mglon-month-slot--last){border-top-left-radius:0;border-bottom-left-radius:0;width:calc(var(--slot-width, 100%) - 4px)!important}:host(.mglon-month-slot--middle){border-radius:0}.mglon-month-slot__inner{display:flex;align-items:center;width:100%;height:100%;padding:0 8px;box-sizing:border-box}.mglon-month-slot__title{display:inline-flex;align-items:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.mglon-month-slot__title .mglon-month-slot__recurrence-icon{flex-shrink:0;margin-right:var(--mglon-schedule-gap-sm, 8px);font-size:1.2em}:host(.mglon-month-slot--idle){cursor:grab;pointer-events:all}:host(.mglon-month-slot--dragging){opacity:.6;cursor:grabbing;box-shadow:var(--mglon-schedule-shadow-lg, 0 10px 25px rgba(0, 0, 0, .15));transform:scale(1.02);z-index:100!important;pointer-events:none}:host(.mglon-month-slot--hovered){background-color:var(--slot-hover);color:var(--slot-text-hover, var(--slot-text))}:host(.mglon-event--all-day){box-shadow:none;border:var(--slot-border, none)}:host(.mglon-event--all-day):hover{border:var(--slot-border, none)}.mglon-event__all-day-icon{flex-shrink:0;margin-right:var(--mglon-schedule-gap-sm, 8px);font-size:1.2em;display:flex!important;align-items:center;justify-content:center}\n"] }]
|
|
2391
|
+
}], propDecorators: { slot: [{ type: i0.Input, args: [{ isSignal: true, alias: "slot", required: true }] }] } });
|
|
2392
|
+
|
|
2393
|
+
class MonthWeekEventsContainer {
|
|
2394
|
+
slots = input.required(...(ngDevMode ? [{ debugName: "slots" }] : []));
|
|
2395
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthWeekEventsContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2396
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: MonthWeekEventsContainer, isStandalone: true, selector: "mglon-month-week-events-container", inputs: { slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style.position": "\"relative\"", "style.width": "\"100%\"", "style.height": "\"100%\"" } }, ngImport: i0, template: "<div class=\"mglon-month-week-events-container\">\n @for (slot of slots(); track slot.id) {\n <mglon-month-slot [slot]=\"slot\"></mglon-month-slot>\n }\n</div>", styles: [".mglon-month-week-events-container{position:relative;display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "component", type: MonthSlot, selector: "mglon-month-slot", inputs: ["slot"] }] });
|
|
2397
|
+
}
|
|
2398
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthWeekEventsContainer, decorators: [{
|
|
2399
|
+
type: Component,
|
|
2400
|
+
args: [{ selector: 'mglon-month-week-events-container', imports: [MonthSlot], host: {
|
|
2401
|
+
'[style.position]': '"relative"',
|
|
2402
|
+
'[style.width]': '"100%"',
|
|
2403
|
+
'[style.height]': '"100%"',
|
|
2404
|
+
}, template: "<div class=\"mglon-month-week-events-container\">\n @for (slot of slots(); track slot.id) {\n <mglon-month-slot [slot]=\"slot\"></mglon-month-slot>\n }\n</div>", styles: [".mglon-month-week-events-container{position:relative;display:block;width:100%;height:100%}\n"] }]
|
|
2405
|
+
}], propDecorators: { slots: [{ type: i0.Input, args: [{ isSignal: true, alias: "slots", required: true }] }] } });
|
|
2406
|
+
|
|
2407
|
+
class EventRendererAdapter {
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
const DEFAULT_SLOT_CONFIG = {
|
|
2411
|
+
slotHeight: SLOT_HEIGHT,
|
|
2412
|
+
slotGap: SLOT_GAP
|
|
2413
|
+
};
|
|
2414
|
+
/**
|
|
2415
|
+
* Calculates the minimum height required to display a given number of event rows.
|
|
2416
|
+
*
|
|
2417
|
+
* Formula: (n * slotHeight) + ((n - 1) * slotGap)
|
|
2418
|
+
* - For n=3 with defaults (20px height, 2px gap):
|
|
2419
|
+
* (3 * 20) + (2 * 2) = 60 + 4 = 64px
|
|
2420
|
+
*
|
|
2421
|
+
* @param rows - Number of event rows to display
|
|
2422
|
+
* @param config - Slot configuration (height and gap)
|
|
2423
|
+
* @returns Minimum height in pixels
|
|
2424
|
+
*/
|
|
2425
|
+
function calculateMinSlotContainerHeight(rows, config = DEFAULT_SLOT_CONFIG) {
|
|
2426
|
+
if (rows <= 0)
|
|
2427
|
+
return 0;
|
|
2428
|
+
const totalSlotHeight = rows * config.slotHeight;
|
|
2429
|
+
const totalGapHeight = (rows - 1) * config.slotGap;
|
|
2430
|
+
return totalSlotHeight + totalGapHeight;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
/**
|
|
2434
|
+
* Extracts the effective date range from any event type.
|
|
2435
|
+
* Normalizes different event structures into a consistent DateRange.
|
|
2436
|
+
*/
|
|
2437
|
+
function getEventDateRange(event) {
|
|
2438
|
+
if (isEvent(event) || isRecurrentEvent(event)) {
|
|
2439
|
+
return {
|
|
2440
|
+
start: startOfDay(event.start),
|
|
2441
|
+
end: endOfDay(event.end)
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
if (isAllDayEvent(event)) {
|
|
2445
|
+
const start = startOfDay(event.date);
|
|
2446
|
+
const end = event.endDate ? endOfDay(event.endDate) : endOfDay(event.date);
|
|
2447
|
+
return { start, end };
|
|
2448
|
+
}
|
|
2449
|
+
// Fallback (should never happen with proper types)
|
|
2450
|
+
return { start: new Date(), end: new Date() };
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
/**
|
|
2454
|
+
* Determines the slot type based on whether the event extends beyond the week boundaries.
|
|
2455
|
+
*
|
|
2456
|
+
* @returns
|
|
2457
|
+
* - 'full': Event starts and ends within this week
|
|
2458
|
+
* - 'first': Event starts this week but continues into next week(s)
|
|
2459
|
+
* - 'last': Event started in previous week(s) and ends this week
|
|
2460
|
+
* - 'middle': Event spans across this entire week (started before, ends after)
|
|
2461
|
+
*/
|
|
2462
|
+
function determineSlotType(eventRange, weekRange) {
|
|
2463
|
+
const startsBeforeWeek = eventRange.start < weekRange.start;
|
|
2464
|
+
const endsAfterWeek = eventRange.end > weekRange.end;
|
|
2465
|
+
if (startsBeforeWeek && endsAfterWeek)
|
|
2466
|
+
return 'middle';
|
|
2467
|
+
if (startsBeforeWeek)
|
|
2468
|
+
return 'last';
|
|
2469
|
+
if (endsAfterWeek)
|
|
2470
|
+
return 'first';
|
|
2471
|
+
return 'full';
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const DAYS_IN_WEEK$1 = 7;
|
|
2475
|
+
const PERCENTAGE_BASE = 100;
|
|
2476
|
+
/**
|
|
2477
|
+
* Calculates the horizontal position as percentages (0-100).
|
|
2478
|
+
* No container dimensions needed - pure percentage-based positioning.
|
|
2479
|
+
*/
|
|
2480
|
+
function calculateHorizontalPosition(clampedStart, clampedEnd, weekStart) {
|
|
2481
|
+
// Calculate day indices (0-6) within the week
|
|
2482
|
+
const startDayIndex = differenceInDays(startOfDay(clampedStart), startOfDay(weekStart));
|
|
2483
|
+
const endDayIndex = differenceInDays(startOfDay(clampedEnd), startOfDay(weekStart));
|
|
2484
|
+
// Number of days the event spans (inclusive)
|
|
2485
|
+
const spanDays = endDayIndex - startDayIndex + 1;
|
|
2486
|
+
return {
|
|
2487
|
+
left: (startDayIndex / DAYS_IN_WEEK$1) * PERCENTAGE_BASE,
|
|
2488
|
+
width: (spanDays / DAYS_IN_WEEK$1) * PERCENTAGE_BASE
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Finds the first available row for an event without overlapping.
|
|
2493
|
+
* Uses a greedy algorithm with proper day-range intersection checking.
|
|
2494
|
+
*
|
|
2495
|
+
* ## How overlap is determined:
|
|
2496
|
+
*
|
|
2497
|
+
* Two events overlap if their day ranges intersect. We use half-open intervals
|
|
2498
|
+
* [startDay, exclusiveEndDay) for comparison:
|
|
2499
|
+
*
|
|
2500
|
+
* ```
|
|
2501
|
+
* Event A: Dec 22-24 → { startDay: Dec 22, exclusiveEndDay: Dec 25 }
|
|
2502
|
+
* Event B: Dec 25 → { startDay: Dec 25, exclusiveEndDay: Dec 26 }
|
|
2503
|
+
*
|
|
2504
|
+
* Intersection check: A.start < B.exclusiveEnd AND B.start < A.exclusiveEnd
|
|
2505
|
+
* Dec 22 < Dec 26 (true) AND Dec 25 < Dec 25 (false)
|
|
2506
|
+
* → NO overlap ✓
|
|
2507
|
+
* ```
|
|
2508
|
+
*
|
|
2509
|
+
* @param clampedStart - Event start date (clamped to week)
|
|
2510
|
+
* @param clampedEnd - Event end date (clamped to week)
|
|
2511
|
+
* @param rowAssignments - Map of row index → array of placed event ranges
|
|
2512
|
+
*/
|
|
2513
|
+
function findAvailableRow(clampedStart, clampedEnd, rowAssignments) {
|
|
2514
|
+
let rowIndex = 0;
|
|
2515
|
+
// Create the day range for the new event
|
|
2516
|
+
const newEventRange = {
|
|
2517
|
+
startDay: startOfDay(clampedStart),
|
|
2518
|
+
exclusiveEndDay: addDays(startOfDay(clampedEnd), 1)
|
|
2519
|
+
};
|
|
2520
|
+
while (true) {
|
|
2521
|
+
const existingRanges = rowAssignments.get(rowIndex) || [];
|
|
2522
|
+
// Check if new event overlaps with any existing event in this row
|
|
2523
|
+
// Using half-open interval intersection: A ∩ B ≠ ∅ ⟺ A.start < B.end AND B.start < A.end
|
|
2524
|
+
const hasOverlap = existingRanges.some(existing => newEventRange.startDay < existing.exclusiveEndDay &&
|
|
2525
|
+
existing.startDay < newEventRange.exclusiveEndDay);
|
|
2526
|
+
if (!hasOverlap) {
|
|
2527
|
+
rowAssignments.set(rowIndex, [...existingRanges, newEventRange]);
|
|
2528
|
+
return rowIndex;
|
|
2529
|
+
}
|
|
2530
|
+
rowIndex++;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Calculates the maximum number of visible rows based on container height.
|
|
2535
|
+
*
|
|
2536
|
+
* @param containerHeight - Height of the container in pixels
|
|
2537
|
+
* @param slotHeight - Height of each slot row in pixels
|
|
2538
|
+
* @param slotGap - Vertical gap between slots in pixels
|
|
2539
|
+
* @returns Maximum number of full rows that fit in the container
|
|
2540
|
+
*
|
|
2541
|
+
* @example
|
|
2542
|
+
* // Container of 66px with 20px slots and 2px gap = 3 rows
|
|
2543
|
+
* // Row 0: 0-20px, Row 1: 22-42px, Row 2: 44-64px
|
|
2544
|
+
* calculateMaxVisibleRows(66, 20, 2) // returns 3
|
|
2545
|
+
*/
|
|
2546
|
+
function calculateMaxVisibleRows(containerHeight, slotHeight, slotGap) {
|
|
2547
|
+
if (containerHeight <= 0 || slotHeight <= 0) {
|
|
2548
|
+
return 0;
|
|
2549
|
+
}
|
|
2550
|
+
// Each row takes: slotHeight + slotGap (except the last one doesn't need gap)
|
|
2551
|
+
// Formula: containerHeight >= (n * slotHeight) + ((n - 1) * slotGap)
|
|
2552
|
+
// Simplified: n = floor((containerHeight + slotGap) / (slotHeight + slotGap))
|
|
2553
|
+
const rowWithGap = slotHeight + slotGap;
|
|
2554
|
+
return Math.floor((containerHeight + slotGap) / rowWithGap);
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
/**
|
|
2558
|
+
* Slices a collection of events into visual slots for a specific week.
|
|
2559
|
+
* Returns percentage-based horizontal positions (no ResizeObserver needed).
|
|
2560
|
+
*
|
|
2561
|
+
* @param events - Array of events to process (should already be filtered for the week)
|
|
2562
|
+
* @param weekRange - The date range of the week { start: Sunday 00:00, end: Saturday 23:59 }
|
|
2563
|
+
* @param config - Optional configuration for slot sizing
|
|
2564
|
+
* @returns Array of SlotModel with calculated positions (left/width as %, top/height as px)
|
|
2565
|
+
*
|
|
2566
|
+
* @example
|
|
2567
|
+
* const slots = sliceEventsByWeek(events, {
|
|
2568
|
+
* start: startOfWeek(date),
|
|
2569
|
+
* end: endOfWeek(date)
|
|
2570
|
+
* })
|
|
2571
|
+
* // Use in template: [style.left.%]="slot.position.left"
|
|
2572
|
+
*/
|
|
2573
|
+
function sliceEventsByWeek(events, weekRange, config = DEFAULT_SLOT_CONFIG) {
|
|
2574
|
+
const slots = [];
|
|
2575
|
+
const rowAssignments = new Map();
|
|
2576
|
+
for (const event of events) {
|
|
2577
|
+
const eventRange = getEventDateRange(event);
|
|
2578
|
+
// Clamp event dates to the week boundaries
|
|
2579
|
+
const clampedStart = max([eventRange.start, weekRange.start]);
|
|
2580
|
+
const clampedEnd = min([eventRange.end, weekRange.end]);
|
|
2581
|
+
// Calculate horizontal position as percentages
|
|
2582
|
+
const { left, width } = calculateHorizontalPosition(clampedStart, clampedEnd, weekRange.start);
|
|
2583
|
+
// Find available row (avoids overlapping)
|
|
2584
|
+
const rowIndex = findAvailableRow(clampedStart, clampedEnd, rowAssignments);
|
|
2585
|
+
// Calculate vertical position in pixels
|
|
2586
|
+
const top = rowIndex * (config.slotHeight + config.slotGap);
|
|
2587
|
+
// Determine slot type based on week boundaries
|
|
2588
|
+
const type = determineSlotType(eventRange, weekRange);
|
|
2589
|
+
// Extract event properties for the slot
|
|
2590
|
+
const isEditable = !event.isReadOnly && !event.isBlocked;
|
|
2591
|
+
slots.push({
|
|
2592
|
+
id: `${event.id}-${weekRange.start.getTime()}`,
|
|
2593
|
+
idEvent: event.id,
|
|
2594
|
+
start: clampedStart,
|
|
2595
|
+
end: clampedEnd,
|
|
2596
|
+
position: {
|
|
2597
|
+
top,
|
|
2598
|
+
left,
|
|
2599
|
+
height: config.slotHeight,
|
|
2600
|
+
width
|
|
2601
|
+
},
|
|
2602
|
+
zIndex: rowIndex + 1,
|
|
2603
|
+
type,
|
|
2604
|
+
color: event.color ?? '',
|
|
2605
|
+
draggable: isEditable,
|
|
2606
|
+
resizable: isEditable
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
return slots;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// ============================================================================
|
|
2613
|
+
// CONSTANTS
|
|
2614
|
+
// ============================================================================
|
|
2615
|
+
const DAYS_IN_WEEK = 7;
|
|
2616
|
+
// ============================================================================
|
|
2617
|
+
// MAIN FUNCTION
|
|
2618
|
+
// ============================================================================
|
|
2619
|
+
/**
|
|
2620
|
+
* Partitions slots into visible and hidden based on the maximum number of rows
|
|
2621
|
+
* that can fit in the container.
|
|
2622
|
+
*
|
|
2623
|
+
* ## How it works:
|
|
2624
|
+
*
|
|
2625
|
+
* 1. Each slot has a `zIndex` which represents its row position (1-indexed).
|
|
2626
|
+
* - zIndex 1 = Row 0 (top row)
|
|
2627
|
+
* - zIndex 2 = Row 1
|
|
2628
|
+
* - etc.
|
|
2629
|
+
*
|
|
2630
|
+
* 2. If a slot's zIndex exceeds maxRows, the entire slot is hidden.
|
|
2631
|
+
* This ensures multi-day events don't partially render.
|
|
2632
|
+
*
|
|
2633
|
+
* 3. For hidden slots, we count how many events are hidden per day column.
|
|
2634
|
+
* This allows showing "+N more" indicators in each affected day.
|
|
2635
|
+
*
|
|
2636
|
+
* ## Visual Example:
|
|
2637
|
+
*
|
|
2638
|
+
* ```
|
|
2639
|
+
* maxRows = 2
|
|
2640
|
+
*
|
|
2641
|
+
* Mon Tue Wed Thu Fri
|
|
2642
|
+
* ┌─────┬─────┬─────┬─────┬─────┐
|
|
2643
|
+
* Row 0 │ A │ A │ A │ │ │ visible (zIndex=1)
|
|
2644
|
+
* ├─────┼─────┼─────┼─────┼─────┤
|
|
2645
|
+
* Row 1 │ │ B │ B │ B │ │ visible (zIndex=2)
|
|
2646
|
+
* ├─────┴─────┴─────┴─────┴─────┤ ← maxRows limit
|
|
2647
|
+
* Row 2 │ │ │ C │ │ │ HIDDEN (zIndex=3)
|
|
2648
|
+
* └─────┴─────┴─────┴─────┴─────┘
|
|
2649
|
+
*
|
|
2650
|
+
* overflowPerDay = [0, 0, 1, 0, 0, 0, 0]
|
|
2651
|
+
* ↑ Wed shows "+1 more"
|
|
2652
|
+
* ```
|
|
2653
|
+
*
|
|
2654
|
+
* @param slots - All slots generated by sliceEventsByWeek
|
|
2655
|
+
* @param maxRows - Maximum number of rows that fit in the container
|
|
2656
|
+
* @param weekStart - Start date of the week (used to calculate day indices)
|
|
2657
|
+
* @returns Partitioned slots with overflow counts per day
|
|
2658
|
+
*/
|
|
2659
|
+
function partitionSlotsByVisibility(slots, maxRows, weekStart) {
|
|
2660
|
+
const visibleSlots = [];
|
|
2661
|
+
const hiddenSlots = [];
|
|
2662
|
+
const overflowPerDay = new Array(DAYS_IN_WEEK).fill(0);
|
|
2663
|
+
// Normalize week start to beginning of day for consistent comparisons
|
|
2664
|
+
const normalizedWeekStart = startOfDay(weekStart);
|
|
2665
|
+
for (const slot of slots) {
|
|
2666
|
+
// zIndex is 1-indexed (row 0 has zIndex 1)
|
|
2667
|
+
// A slot is visible if its row fits within maxRows
|
|
2668
|
+
const isVisible = slot.zIndex <= maxRows;
|
|
2669
|
+
if (isVisible) {
|
|
2670
|
+
visibleSlots.push(slot);
|
|
2671
|
+
}
|
|
2672
|
+
else {
|
|
2673
|
+
hiddenSlots.push(slot);
|
|
2674
|
+
// Calculate which days this hidden slot spans
|
|
2675
|
+
// and increment the overflow counter for each affected day
|
|
2676
|
+
incrementOverflowForSlotDays(slot, normalizedWeekStart, overflowPerDay);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
return { visibleSlots, hiddenSlots, overflowPerDay };
|
|
2680
|
+
}
|
|
2681
|
+
// ============================================================================
|
|
2682
|
+
// HELPER FUNCTIONS
|
|
2683
|
+
// ============================================================================
|
|
2684
|
+
/**
|
|
2685
|
+
* Increments the overflow counter for each day that a slot occupies.
|
|
2686
|
+
*
|
|
2687
|
+
* This ensures that the "+N more" indicator correctly reflects
|
|
2688
|
+
* all hidden events affecting each day column.
|
|
2689
|
+
*
|
|
2690
|
+
* @param slot - The hidden slot
|
|
2691
|
+
* @param weekStart - Normalized start of the week
|
|
2692
|
+
* @param overflowPerDay - Array to mutate with incremented counts
|
|
2693
|
+
*/
|
|
2694
|
+
function incrementOverflowForSlotDays(slot, weekStart, overflowPerDay) {
|
|
2695
|
+
// Calculate day indices (0-6) within the week
|
|
2696
|
+
const startDayIndex = differenceInDays(startOfDay(slot.start), weekStart);
|
|
2697
|
+
const endDayIndex = differenceInDays(startOfDay(slot.end), weekStart);
|
|
2698
|
+
// Clamp indices to valid range [0, 6]
|
|
2699
|
+
// This handles edge cases where slot dates might extend beyond the week
|
|
2700
|
+
const safeStartDay = Math.max(0, Math.min(startDayIndex, DAYS_IN_WEEK - 1));
|
|
2701
|
+
const safeEndDay = Math.max(0, Math.min(endDayIndex, DAYS_IN_WEEK - 1));
|
|
2702
|
+
// Increment counter for each day the slot spans
|
|
2703
|
+
for (let day = safeStartDay; day <= safeEndDay; day++) {
|
|
2704
|
+
overflowPerDay[day]++;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// Public API
|
|
2709
|
+
|
|
2710
|
+
class MonthWeek extends EventRendererAdapter {
|
|
2711
|
+
store = inject(CalendarStore);
|
|
2712
|
+
week = input.required(...(ngDevMode ? [{ debugName: "week" }] : []));
|
|
2713
|
+
/** Index of this week in the month (0-based) */
|
|
2714
|
+
weekIndex = input(0, ...(ngDevMode ? [{ debugName: "weekIndex" }] : []));
|
|
2715
|
+
/** Whether this week is expanded to show all events */
|
|
2716
|
+
expanded = input(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
2717
|
+
top = CELL_HEADER_HEIGHT;
|
|
2718
|
+
/** Minimum height for the week row from store config */
|
|
2719
|
+
minHeight = this.store.minWeekRowHeight;
|
|
2720
|
+
/**
|
|
2721
|
+
* Computes the week's date range from first to last day.
|
|
2722
|
+
*/
|
|
2723
|
+
weekRange = computed(() => {
|
|
2724
|
+
const weekDays = this.week().days;
|
|
2725
|
+
if (weekDays.length === 0) {
|
|
2726
|
+
return { start: new Date(), end: new Date() };
|
|
2727
|
+
}
|
|
2728
|
+
return {
|
|
2729
|
+
start: startOfDay(weekDays[0].date),
|
|
2730
|
+
end: endOfDay(weekDays[weekDays.length - 1].date)
|
|
2731
|
+
};
|
|
2732
|
+
}, ...(ngDevMode ? [{ debugName: "weekRange" }] : []));
|
|
2733
|
+
/**
|
|
2734
|
+
* Available height for event slots (total row height minus cell header).
|
|
2735
|
+
*/
|
|
2736
|
+
availableHeight = computed(() => {
|
|
2737
|
+
return Math.max(0, this.store.weekRowHeight() - CELL_HEADER_HEIGHT);
|
|
2738
|
+
}, ...(ngDevMode ? [{ debugName: "availableHeight" }] : []));
|
|
2739
|
+
/**
|
|
2740
|
+
* Filters currentViewEvents to only include events
|
|
2741
|
+
* that intersect with this week's date range.
|
|
2742
|
+
*/
|
|
2743
|
+
events = computed(() => {
|
|
2744
|
+
const weekDays = this.week().days;
|
|
2745
|
+
if (weekDays.length === 0)
|
|
2746
|
+
return [];
|
|
2747
|
+
return this.filterEvents(this.store.currentViewEvents(), this.weekRange());
|
|
2748
|
+
}, ...(ngDevMode ? [{ debugName: "events" }] : []));
|
|
2749
|
+
/**
|
|
2750
|
+
* Computed slots for this week's events.
|
|
2751
|
+
*/
|
|
2752
|
+
slots = computed(() => {
|
|
2753
|
+
return this.createSlots(this.events());
|
|
2754
|
+
}, ...(ngDevMode ? [{ debugName: "slots" }] : []));
|
|
2755
|
+
/**
|
|
2756
|
+
* Maximum row number used by any slot.
|
|
2757
|
+
* Derived from the slot's top position.
|
|
2758
|
+
*/
|
|
2759
|
+
maxRow = computed(() => {
|
|
2760
|
+
const allSlots = this.slots();
|
|
2761
|
+
if (allSlots.length === 0)
|
|
2762
|
+
return 0;
|
|
2763
|
+
// Calculate row from top position: row = top / (SLOT_HEIGHT + SLOT_GAP)
|
|
2764
|
+
const rowHeight = SLOT_HEIGHT + SLOT_GAP;
|
|
2765
|
+
return Math.max(...allSlots.map(slot => Math.floor(slot.position.top / rowHeight)));
|
|
2766
|
+
}, ...(ngDevMode ? [{ debugName: "maxRow" }] : []));
|
|
2767
|
+
/**
|
|
2768
|
+
* Height needed to show all events when expanded.
|
|
2769
|
+
* Calculated based on the number of slot rows plus bottom padding.
|
|
2770
|
+
*/
|
|
2771
|
+
expandedHeight = computed(() => {
|
|
2772
|
+
const rows = this.maxRow() + 1; // rows are 0-indexed
|
|
2773
|
+
// Formula: header + (rows * slot height) + (rows * gap)
|
|
2774
|
+
return CELL_HEADER_HEIGHT + (rows * SLOT_HEIGHT) + (rows * SLOT_GAP);
|
|
2775
|
+
}, ...(ngDevMode ? [{ debugName: "expandedHeight" }] : []));
|
|
2776
|
+
/**
|
|
2777
|
+
* Whether the week has more events than can fit in the minimum height.
|
|
2778
|
+
* Used to conditionally show the expand toggle.
|
|
2779
|
+
*/
|
|
2780
|
+
hasOverflow = computed(() => {
|
|
2781
|
+
return this.expandedHeight() > this.minHeight();
|
|
2782
|
+
}, ...(ngDevMode ? [{ debugName: "hasOverflow" }] : []));
|
|
2783
|
+
/**
|
|
2784
|
+
* Dynamic height for the week based on expansion state.
|
|
2785
|
+
* Returns the expanded height or minimum height.
|
|
2786
|
+
*/
|
|
2787
|
+
weekHeight = computed(() => {
|
|
2788
|
+
if (this.expanded()) {
|
|
2789
|
+
// When expanded, use the larger of expandedHeight or minHeight
|
|
2790
|
+
return Math.max(this.expandedHeight(), this.minHeight());
|
|
2791
|
+
}
|
|
2792
|
+
return null; // Use min-height only (CSS handles it)
|
|
2793
|
+
}, ...(ngDevMode ? [{ debugName: "weekHeight" }] : []));
|
|
2794
|
+
filterEvents(events, dateRange) {
|
|
2795
|
+
return events.filter(event => isEventInRange(event, dateRange));
|
|
2796
|
+
}
|
|
2797
|
+
createSlots(events) {
|
|
2798
|
+
if (events.length === 0)
|
|
2799
|
+
return [];
|
|
2800
|
+
return sliceEventsByWeek(events, this.weekRange());
|
|
2801
|
+
}
|
|
2802
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthWeek, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2803
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: MonthWeek, isStandalone: true, selector: "mglon-month-week", inputs: { week: { classPropertyName: "week", publicName: "week", isSignal: true, isRequired: true, transformFunction: null }, weekIndex: { classPropertyName: "weekIndex", publicName: "weekIndex", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.height.px": "weekHeight()", "class.mglon-month-week--expanded": "expanded()" } }, usesInheritance: true, ngImport: i0, template: "<div class=\"mglon-month-week\" [class.mglon-month-week--expanded]=\"expanded()\">\n\n <div class=\"mglon-month-week__days\" [style.min-height.px]=\"minHeight()\">\n @for (day of week().days; track day.date.getTime()) {\n <mglon-month-cell [day]=\"day\" [attr.data-date]=\"day.date.toISOString()\"></mglon-month-cell>\n }\n </div>\n\n <div #weekEventsContainer class=\"mglon-month-week__events\" [style.top.px]=\"top\">\n <mglon-month-week-events-container [slots]=\"slots()\"></mglon-month-week-events-container>\n </div>\n</div>", styles: [".mglon-month-week{position:relative;height:100%}.mglon-month-week__days{display:var(--mglon-schedule-month-week-display);grid-template-columns:var(--mglon-schedule-month-week-columns);min-height:var(--mglon-schedule-month-week-min-height);height:100%;border-bottom:var(--mglon-schedule-month-week-border-bottom-width) solid var(--mglon-schedule-border)}.mglon-month-week__days:last-child{border-bottom:none}.mglon-month-week__events{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host{display:block}:host(.mglon-month-week--expanded) .mglon-month-week__events{overflow:visible}\n"], dependencies: [{ kind: "component", type: MonthCell, selector: "mglon-month-cell", inputs: ["day"] }, { kind: "component", type: MonthWeekEventsContainer, selector: "mglon-month-week-events-container", inputs: ["slots"] }] });
|
|
2804
|
+
}
|
|
2805
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthWeek, decorators: [{
|
|
2806
|
+
type: Component,
|
|
2807
|
+
args: [{ selector: 'mglon-month-week', imports: [MonthCell, MonthWeekEventsContainer], host: {
|
|
2808
|
+
'[style.height.px]': 'weekHeight()',
|
|
2809
|
+
'[class.mglon-month-week--expanded]': 'expanded()'
|
|
2810
|
+
}, template: "<div class=\"mglon-month-week\" [class.mglon-month-week--expanded]=\"expanded()\">\n\n <div class=\"mglon-month-week__days\" [style.min-height.px]=\"minHeight()\">\n @for (day of week().days; track day.date.getTime()) {\n <mglon-month-cell [day]=\"day\" [attr.data-date]=\"day.date.toISOString()\"></mglon-month-cell>\n }\n </div>\n\n <div #weekEventsContainer class=\"mglon-month-week__events\" [style.top.px]=\"top\">\n <mglon-month-week-events-container [slots]=\"slots()\"></mglon-month-week-events-container>\n </div>\n</div>", styles: [".mglon-month-week{position:relative;height:100%}.mglon-month-week__days{display:var(--mglon-schedule-month-week-display);grid-template-columns:var(--mglon-schedule-month-week-columns);min-height:var(--mglon-schedule-month-week-min-height);height:100%;border-bottom:var(--mglon-schedule-month-week-border-bottom-width) solid var(--mglon-schedule-border)}.mglon-month-week__days:last-child{border-bottom:none}.mglon-month-week__events{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host{display:block}:host(.mglon-month-week--expanded) .mglon-month-week__events{overflow:visible}\n"] }]
|
|
2811
|
+
}], propDecorators: { week: [{ type: i0.Input, args: [{ isSignal: true, alias: "week", required: true }] }], weekIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekIndex", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }] } });
|
|
2812
|
+
|
|
2813
|
+
class ResizeObserverDirective {
|
|
2814
|
+
elementRef;
|
|
2815
|
+
mglonResizeObserver = new EventEmitter();
|
|
2816
|
+
observer = null;
|
|
2817
|
+
constructor(elementRef) {
|
|
2818
|
+
this.elementRef = elementRef;
|
|
2819
|
+
}
|
|
2820
|
+
ngOnInit() {
|
|
2821
|
+
this.observer = new ResizeObserver((entries) => {
|
|
2822
|
+
const entry = entries[0];
|
|
2823
|
+
if (entry) {
|
|
2824
|
+
this.mglonResizeObserver.emit({
|
|
2825
|
+
width: entry.contentRect.width,
|
|
2826
|
+
height: entry.contentRect.height,
|
|
2827
|
+
entry
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
this.observer.observe(this.elementRef.nativeElement);
|
|
2832
|
+
}
|
|
2833
|
+
ngOnDestroy() {
|
|
2834
|
+
this.observer?.disconnect();
|
|
2835
|
+
this.observer = null;
|
|
2836
|
+
}
|
|
2837
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResizeObserverDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2838
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: ResizeObserverDirective, isStandalone: true, selector: "[mglonResizeObserver]", outputs: { mglonResizeObserver: "mglonResizeObserver" }, ngImport: i0 });
|
|
2839
|
+
}
|
|
2840
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResizeObserverDirective, decorators: [{
|
|
2841
|
+
type: Directive,
|
|
2842
|
+
args: [{
|
|
2843
|
+
selector: '[mglonResizeObserver]',
|
|
2844
|
+
standalone: true
|
|
2845
|
+
}]
|
|
2846
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { mglonResizeObserver: [{
|
|
2847
|
+
type: Output
|
|
2848
|
+
}] } });
|
|
2849
|
+
|
|
2850
|
+
/**
|
|
2851
|
+
* Month calendar grid component that displays a full month view with weeks and days.
|
|
2852
|
+
*
|
|
2853
|
+
* Implements the Selectable interface to enable date selection via mouse interaction.
|
|
2854
|
+
* Uses the SelectableDirective to handle all selection logic.
|
|
2855
|
+
*
|
|
2856
|
+
* Features:
|
|
2857
|
+
* - Displays 4-6 weeks dynamically
|
|
2858
|
+
* - Shows padding days from previous/next months
|
|
2859
|
+
* - Supports mouse-based date range selection
|
|
2860
|
+
* - Expandable week rows via toggle buttons
|
|
2861
|
+
*
|
|
2862
|
+
* @example
|
|
2863
|
+
* ```html
|
|
2864
|
+
* <mglon-month-grid
|
|
2865
|
+
* [currentDate]="selectedDate"
|
|
2866
|
+
* (selectionEnd)="onDateRangeSelected($event)">
|
|
2867
|
+
* </mglon-month-grid>
|
|
2868
|
+
* ```
|
|
2869
|
+
*/
|
|
2870
|
+
class MonthGrid {
|
|
2871
|
+
elementRef = inject(ElementRef);
|
|
2872
|
+
store = inject(CalendarStore);
|
|
2873
|
+
/**
|
|
2874
|
+
* Reference to the SelectableDirective instance.
|
|
2875
|
+
* Used to access selection state (selection rectangle and isSelecting flag).
|
|
2876
|
+
*/
|
|
2877
|
+
selectableDirective = viewChild(SelectableDirective, ...(ngDevMode ? [{ debugName: "selectableDirective" }] : []));
|
|
2878
|
+
/**
|
|
2879
|
+
* The date to display the calendar for.
|
|
2880
|
+
* Can be any date within the target month.
|
|
2881
|
+
* @default new Date() (current month)
|
|
2882
|
+
*/
|
|
2883
|
+
currentDate = input(new Date(), ...(ngDevMode ? [{ debugName: "currentDate" }] : []));
|
|
2884
|
+
/**
|
|
2885
|
+
* Computed calendar grid for the current month.
|
|
2886
|
+
* Automatically updates when currentDate changes.
|
|
2887
|
+
* Returns an array of weeks, each containing 7 days.
|
|
2888
|
+
*/
|
|
2889
|
+
weeks = computed(() => {
|
|
2890
|
+
console.log('weeks cambiaron');
|
|
2891
|
+
return getMonthCalendarGrid(this.currentDate());
|
|
2892
|
+
}, ...(ngDevMode ? [{ debugName: "weeks" }] : []));
|
|
2893
|
+
/**
|
|
2894
|
+
* Dynamic grid-template-rows based on actual number of weeks.
|
|
2895
|
+
* Uses minmax(0, auto) to allow expanded weeks to grow beyond equal distribution.
|
|
2896
|
+
*/
|
|
2897
|
+
gridTemplateRows = computed(() => `repeat(${this.weeks().length}, minmax(0, auto))`, ...(ngDevMode ? [{ debugName: "gridTemplateRows" }] : []));
|
|
2898
|
+
/**
|
|
2899
|
+
* Calculates which weeks have content that exceeds minimum height.
|
|
2900
|
+
* Returns a Map of weekIndex -> hasOverflow boolean.
|
|
2901
|
+
*/
|
|
2902
|
+
weekOverflowMap = computed(() => {
|
|
2903
|
+
const weeks = this.weeks();
|
|
2904
|
+
const events = this.store.currentViewEvents();
|
|
2905
|
+
const minHeight = this.store.minWeekRowHeight();
|
|
2906
|
+
const result = new Map();
|
|
2907
|
+
weeks.forEach((week, index) => {
|
|
2908
|
+
const weekDays = week.days;
|
|
2909
|
+
if (weekDays.length === 0) {
|
|
2910
|
+
result.set(index, false);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
const weekRange = {
|
|
2914
|
+
start: startOfDay(weekDays[0].date),
|
|
2915
|
+
end: endOfDay(weekDays[weekDays.length - 1].date)
|
|
2916
|
+
};
|
|
2917
|
+
const weekEvents = events.filter(e => isEventInRange(e, weekRange));
|
|
2918
|
+
if (weekEvents.length === 0) {
|
|
2919
|
+
result.set(index, false);
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
const slots = sliceEventsByWeek(weekEvents, weekRange);
|
|
2923
|
+
const rowHeight = SLOT_HEIGHT + SLOT_GAP;
|
|
2924
|
+
const maxRow = Math.max(...slots.map(slot => Math.floor(slot.position.top / rowHeight)));
|
|
2925
|
+
const rows = maxRow + 1;
|
|
2926
|
+
const expandedHeight = CELL_HEADER_HEIGHT + (rows * SLOT_HEIGHT) + ((rows - 1) * SLOT_GAP) + SLOT_GAP;
|
|
2927
|
+
result.set(index, expandedHeight > minHeight);
|
|
2928
|
+
});
|
|
2929
|
+
return result;
|
|
2930
|
+
}, ...(ngDevMode ? [{ debugName: "weekOverflowMap" }] : []));
|
|
2931
|
+
/**
|
|
2932
|
+
* Checks if a week has overflowing content.
|
|
2933
|
+
* @param weekIndex - Index of the week to check
|
|
2934
|
+
* @returns true if the week has more events than can fit
|
|
2935
|
+
*/
|
|
2936
|
+
hasOverflow(weekIndex) {
|
|
2937
|
+
return this.weekOverflowMap().get(weekIndex) ?? false;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Implements Selectable.getDateFromPoint()
|
|
2941
|
+
*
|
|
2942
|
+
* Translates visual coordinates (relative to the grid container) into a calendar date.
|
|
2943
|
+
* This enables the SelectableDirective to determine which date the user is selecting.
|
|
2944
|
+
*
|
|
2945
|
+
* Algorithm:
|
|
2946
|
+
* 1. Convert relative coordinates to absolute viewport coordinates
|
|
2947
|
+
* 2. Use document.elementFromPoint to find the cell element under the cursor
|
|
2948
|
+
* 3. Extract the date from the cell's data-date attribute
|
|
2949
|
+
* 4. Find and return the corresponding CalendarDay object
|
|
2950
|
+
*
|
|
2951
|
+
* @param x - X coordinate relative to the grid container
|
|
2952
|
+
* @param y - Y coordinate relative to the grid container
|
|
2953
|
+
* @returns Object with date property, or null if no date found at coordinates
|
|
2954
|
+
*/
|
|
2955
|
+
getDateFromPoint(x, y) {
|
|
2956
|
+
// Convert relative coordinates to absolute viewport coordinates
|
|
2957
|
+
const absoluteCoords = this.getAbsoluteCoordinates(x, y);
|
|
2958
|
+
if (!absoluteCoords)
|
|
2959
|
+
return null;
|
|
2960
|
+
// Find the calendar cell element at the cursor position
|
|
2961
|
+
const cellElement = this.findCellElementAtPoint(absoluteCoords.x, absoluteCoords.y);
|
|
2962
|
+
if (!cellElement)
|
|
2963
|
+
return null;
|
|
2964
|
+
// Extract and parse the date from the cell
|
|
2965
|
+
const date = this.extractDateFromCell(cellElement);
|
|
2966
|
+
if (!date)
|
|
2967
|
+
return null;
|
|
2968
|
+
return { date };
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Converts coordinates relative to the grid to absolute viewport coordinates
|
|
2972
|
+
*
|
|
2973
|
+
* @param relativeX - X coordinate relative to grid container
|
|
2974
|
+
* @param relativeY - Y coordinate relative to grid container
|
|
2975
|
+
* @returns Absolute coordinates or null if grid element not found
|
|
2976
|
+
*/
|
|
2977
|
+
getAbsoluteCoordinates(relativeX, relativeY) {
|
|
2978
|
+
const gridElement = this.elementRef.nativeElement;
|
|
2979
|
+
if (!gridElement)
|
|
2980
|
+
return null;
|
|
2981
|
+
const gridRect = gridElement.getBoundingClientRect();
|
|
2982
|
+
return {
|
|
2983
|
+
x: gridRect.left + relativeX,
|
|
2984
|
+
y: gridRect.top + relativeY
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Finds the month cell element at the given viewport coordinates
|
|
2989
|
+
*
|
|
2990
|
+
* @param absoluteX - Absolute X coordinate in viewport
|
|
2991
|
+
* @param absoluteY - Absolute Y coordinate in viewport
|
|
2992
|
+
* @returns The mglon-month-cell element or null if not found
|
|
2993
|
+
*/
|
|
2994
|
+
findCellElementAtPoint(absoluteX, absoluteY) {
|
|
2995
|
+
const element = document.elementFromPoint(absoluteX, absoluteY);
|
|
2996
|
+
if (!element)
|
|
2997
|
+
return null;
|
|
2998
|
+
// Walk up the DOM tree to find the month-cell element
|
|
2999
|
+
return element.closest('mglon-month-cell');
|
|
3000
|
+
}
|
|
3001
|
+
/**
|
|
3002
|
+
* Extracts the date from a month cell element's data attribute
|
|
3003
|
+
*
|
|
3004
|
+
* @param cellElement - The mglon-month-cell element
|
|
3005
|
+
* @returns The date object or null if date attribute is missing/invalid
|
|
3006
|
+
*/
|
|
3007
|
+
extractDateFromCell(cellElement) {
|
|
3008
|
+
const dateStr = cellElement.getAttribute('data-date');
|
|
3009
|
+
if (!dateStr)
|
|
3010
|
+
return null;
|
|
3011
|
+
return new Date(dateStr);
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Handles resize events from the first week container.
|
|
3015
|
+
* Stores the height in CalendarStore to be shared with all weeks.
|
|
3016
|
+
*/
|
|
3017
|
+
onWeekResize(event) {
|
|
3018
|
+
console.log('week resize', event);
|
|
3019
|
+
this.store.setWeekRowHeight(event.height);
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* Toggles the expansion state of a week.
|
|
3023
|
+
* @param weekIndex - Index of the week to toggle (0-based)
|
|
3024
|
+
*/
|
|
3025
|
+
onToggleWeek(weekIndex) {
|
|
3026
|
+
this.store.toggleWeekExpansion(weekIndex);
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Checks if a week is currently expanded.
|
|
3030
|
+
* @param weekIndex - Index of the week to check
|
|
3031
|
+
* @returns true if the week is expanded
|
|
3032
|
+
*/
|
|
3033
|
+
isWeekExpanded(weekIndex) {
|
|
3034
|
+
return this.store.expandedWeekIndex() === weekIndex;
|
|
3035
|
+
}
|
|
3036
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3037
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: MonthGrid, isStandalone: true, selector: "mglon-month-grid", inputs: { currentDate: { classPropertyName: "currentDate", publicName: "currentDate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "selectableDirective", first: true, predicate: SelectableDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mglon-month-grid\" [mglonSelectable]=\"this\" [style.grid-template-rows]=\"gridTemplateRows()\">\n @for (week of weeks(); track $index) {\n <!-- Toggle button cell (only show button if week has overflow) -->\n <div class=\"mglon-month-grid__toggle\">\n @if (hasOverflow($index)) {\n <button mglon-icon-button size=\"xs\" [rounded]=\"'full'\" class=\"mglon-month-grid__toggle-btn\"\n [class.mglon-month-grid__toggle-btn--expanded]=\"isWeekExpanded($index)\" (click)=\"onToggleWeek($index)\">\n <mglon-icon name=\"chevron-right\"></mglon-icon>\n </button>\n }\n </div>\n\n <!-- Week content -->\n @if ($first) {\n <mglon-month-week [week]=\"week\" [weekIndex]=\"$index\" [expanded]=\"isWeekExpanded($index)\"\n (mglonResizeObserver)=\"onWeekResize($event)\">\n </mglon-month-week>\n } @else {\n <mglon-month-week [week]=\"week\" [weekIndex]=\"$index\" [expanded]=\"isWeekExpanded($index)\">\n </mglon-month-week>\n }\n }\n\n <!-- Selection overlay -->\n @if (selectableDirective()) {\n <mglon-selection [selection]=\"selectableDirective()!.selection()\"\n [isSelecting]=\"selectableDirective()!.isSelecting()\">\n </mglon-selection>\n }\n</div>", styles: [":host{display:block;height:100%;width:100%}.mglon-month-grid{display:grid;grid-template-columns:32px 1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none;cursor:auto}.mglon-month-grid__toggle{position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky, 10);background-color:var(--mglon-schedule-surface, #fff);display:flex;flex-direction:column}.mglon-month-grid__toggle-btn{flex:0 0 auto;display:flex;align-items:flex-start;justify-content:center;padding-top:var(--mglon-schedule-padding-xs, 4px);color:var(--mglon-schedule-text-secondary);transition:color var(--mglon-schedule-transition-duration, .2s) var(--mglon-schedule-transition-easing, ease)}.mglon-month-grid__toggle-btn mglon-icon{transition:transform var(--mglon-schedule-transition-duration, .2s) var(--mglon-schedule-transition-easing, ease)}.mglon-month-grid__toggle-btn:hover{color:var(--mglon-schedule-text-primary)}.mglon-month-grid__toggle-btn--expanded mglon-icon{transform:rotate(90deg)}\n"], dependencies: [{ kind: "component", type: Selection, selector: "mglon-selection", inputs: ["selection", "isSelecting"] }, { kind: "directive", type: SelectableDirective, selector: "[mglonSelectable]", inputs: ["mglonSelectable"], outputs: ["selectionStart", "selectionChange", "selectionEnd"] }, { kind: "component", type: MonthWeek, selector: "mglon-month-week", inputs: ["week", "weekIndex", "expanded"] }, { kind: "directive", type: ResizeObserverDirective, selector: "[mglonResizeObserver]", outputs: ["mglonResizeObserver"] }, { kind: "component", type: IconButtonComponent, selector: "button[mglon-icon-button], a[mglon-icon-button]", inputs: ["appereance", "color", "size", "rounded", "active", "disabled"] }, { kind: "component", type: IconComponent, selector: "mglon-icon", inputs: ["name"] }] });
|
|
3038
|
+
}
|
|
3039
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthGrid, decorators: [{
|
|
3040
|
+
type: Component,
|
|
3041
|
+
args: [{ selector: 'mglon-month-grid', imports: [Selection, SelectableDirective, MonthWeek, ResizeObserverDirective, IconButtonComponent, IconComponent], template: "<div class=\"mglon-month-grid\" [mglonSelectable]=\"this\" [style.grid-template-rows]=\"gridTemplateRows()\">\n @for (week of weeks(); track $index) {\n <!-- Toggle button cell (only show button if week has overflow) -->\n <div class=\"mglon-month-grid__toggle\">\n @if (hasOverflow($index)) {\n <button mglon-icon-button size=\"xs\" [rounded]=\"'full'\" class=\"mglon-month-grid__toggle-btn\"\n [class.mglon-month-grid__toggle-btn--expanded]=\"isWeekExpanded($index)\" (click)=\"onToggleWeek($index)\">\n <mglon-icon name=\"chevron-right\"></mglon-icon>\n </button>\n }\n </div>\n\n <!-- Week content -->\n @if ($first) {\n <mglon-month-week [week]=\"week\" [weekIndex]=\"$index\" [expanded]=\"isWeekExpanded($index)\"\n (mglonResizeObserver)=\"onWeekResize($event)\">\n </mglon-month-week>\n } @else {\n <mglon-month-week [week]=\"week\" [weekIndex]=\"$index\" [expanded]=\"isWeekExpanded($index)\">\n </mglon-month-week>\n }\n }\n\n <!-- Selection overlay -->\n @if (selectableDirective()) {\n <mglon-selection [selection]=\"selectableDirective()!.selection()\"\n [isSelecting]=\"selectableDirective()!.isSelecting()\">\n </mglon-selection>\n }\n</div>", styles: [":host{display:block;height:100%;width:100%}.mglon-month-grid{display:grid;grid-template-columns:32px 1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none;cursor:auto}.mglon-month-grid__toggle{position:sticky;left:0;z-index:var(--mglon-schedule-z-sticky, 10);background-color:var(--mglon-schedule-surface, #fff);display:flex;flex-direction:column}.mglon-month-grid__toggle-btn{flex:0 0 auto;display:flex;align-items:flex-start;justify-content:center;padding-top:var(--mglon-schedule-padding-xs, 4px);color:var(--mglon-schedule-text-secondary);transition:color var(--mglon-schedule-transition-duration, .2s) var(--mglon-schedule-transition-easing, ease)}.mglon-month-grid__toggle-btn mglon-icon{transition:transform var(--mglon-schedule-transition-duration, .2s) var(--mglon-schedule-transition-easing, ease)}.mglon-month-grid__toggle-btn:hover{color:var(--mglon-schedule-text-primary)}.mglon-month-grid__toggle-btn--expanded mglon-icon{transform:rotate(90deg)}\n"] }]
|
|
3042
|
+
}], propDecorators: { selectableDirective: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SelectableDirective), { isSignal: true }] }], currentDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentDate", required: false }] }] } });
|
|
3043
|
+
|
|
3044
|
+
class MonthView {
|
|
3045
|
+
calendarStore = inject(CalendarStore);
|
|
3046
|
+
// Get month grid element reference
|
|
3047
|
+
gridElement = viewChild('gridRef', ...(ngDevMode ? [{ debugName: "gridElement" }] : []));
|
|
3048
|
+
constructor() {
|
|
3049
|
+
// Set up grid synchronization after view init
|
|
3050
|
+
afterNextRender(() => {
|
|
3051
|
+
// Logic for grid sizing can remain if needed for the grid itself
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3055
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.6", type: MonthView, isStandalone: true, selector: "mglon-month-view", viewQueries: [{ propertyName: "gridElement", first: true, predicate: ["gridRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mglon-month-view\">\n <!-- Single scrollable container for header and grid -->\n <div class=\"mglon-month-view__content\">\n <!-- Header wrapper -->\n <div class=\"mglon-month-view__header-wrapper\">\n <mglon-month-header></mglon-month-header>\n </div>\n\n <!-- Grid container with reference -->\n <div class=\"mglon-month-view__grid-container\" #gridRef>\n <mglon-month-grid [currentDate]=\"calendarStore.currentDate()\"></mglon-month-grid>\n </div>\n\n </div>\n</div>", styles: [":host{display:block;height:100%}.mglon-month-view{height:100%}.mglon-month-view__content{display:grid;grid-template-rows:auto 1fr;height:100%;overflow-x:auto;overflow-y:auto;scrollbar-gutter:stable;min-width:0}.mglon-month-view__header-wrapper{position:sticky;top:0;z-index:var(--mglon-schedule-z-sticky, 10);background-color:var(--mglon-schedule-surface, #fff)}.mglon-month-view__header-wrapper>mglon-month-header{min-width:700px}.mglon-month-view__grid-container{position:relative;min-width:0}.mglon-month-view__grid-container>mglon-month-grid{min-width:700px}.mglon-month-view__events-layer{position:absolute;inset:0;pointer-events:none}.mglon-month-view__events-layer>*{pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MonthHeader, selector: "mglon-month-header" }, { kind: "component", type: MonthGrid, selector: "mglon-month-grid", inputs: ["currentDate"] }] });
|
|
3056
|
+
}
|
|
3057
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MonthView, decorators: [{
|
|
3058
|
+
type: Component,
|
|
3059
|
+
args: [{ selector: 'mglon-month-view', standalone: true, imports: [CommonModule, MonthHeader, MonthGrid], template: "<div class=\"mglon-month-view\">\n <!-- Single scrollable container for header and grid -->\n <div class=\"mglon-month-view__content\">\n <!-- Header wrapper -->\n <div class=\"mglon-month-view__header-wrapper\">\n <mglon-month-header></mglon-month-header>\n </div>\n\n <!-- Grid container with reference -->\n <div class=\"mglon-month-view__grid-container\" #gridRef>\n <mglon-month-grid [currentDate]=\"calendarStore.currentDate()\"></mglon-month-grid>\n </div>\n\n </div>\n</div>", styles: [":host{display:block;height:100%}.mglon-month-view{height:100%}.mglon-month-view__content{display:grid;grid-template-rows:auto 1fr;height:100%;overflow-x:auto;overflow-y:auto;scrollbar-gutter:stable;min-width:0}.mglon-month-view__header-wrapper{position:sticky;top:0;z-index:var(--mglon-schedule-z-sticky, 10);background-color:var(--mglon-schedule-surface, #fff)}.mglon-month-view__header-wrapper>mglon-month-header{min-width:700px}.mglon-month-view__grid-container{position:relative;min-width:0}.mglon-month-view__grid-container>mglon-month-grid{min-width:700px}.mglon-month-view__events-layer{position:absolute;inset:0;pointer-events:none}.mglon-month-view__events-layer>*{pointer-events:auto}\n"] }]
|
|
3060
|
+
}], ctorParameters: () => [], propDecorators: { gridElement: [{ type: i0.ViewChild, args: ['gridRef', { isSignal: true }] }] } });
|
|
3061
|
+
|
|
3062
|
+
class Avatar {
|
|
3063
|
+
/** Image source URL (optional) */
|
|
3064
|
+
src = input('', ...(ngDevMode ? [{ debugName: "src" }] : []));
|
|
3065
|
+
/** Display name to derive initials from (optional) */
|
|
3066
|
+
name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
3067
|
+
/** Background color for initials placeholder (optional) */
|
|
3068
|
+
color = input('', ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
3069
|
+
/** Alt text for accessibility */
|
|
3070
|
+
alt = input('Avatar', ...(ngDevMode ? [{ debugName: "alt" }] : []));
|
|
3071
|
+
/** Size variant: xs (24px), sm (32px), md (40px), lg (48px), xl (64px) */
|
|
3072
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
3073
|
+
/** Show border around avatar */
|
|
3074
|
+
showBorder = input(false, ...(ngDevMode ? [{ debugName: "showBorder" }] : []));
|
|
3075
|
+
/** Border color (CSS color value) */
|
|
3076
|
+
borderColor = input('var(--ngx-cal-primary)', ...(ngDevMode ? [{ debugName: "borderColor" }] : []));
|
|
3077
|
+
/** Inactive state - applies grayscale and hides border */
|
|
3078
|
+
inactive = input(false, ...(ngDevMode ? [{ debugName: "inactive" }] : []));
|
|
3079
|
+
/** Track if image failed to load */
|
|
3080
|
+
imageError = false;
|
|
3081
|
+
/** Computed initials from the name */
|
|
3082
|
+
initials = computed(() => {
|
|
3083
|
+
const nameStr = this.name();
|
|
3084
|
+
if (!nameStr)
|
|
3085
|
+
return '';
|
|
3086
|
+
const parts = nameStr.trim().split(/\s+/);
|
|
3087
|
+
if (parts.length === 0)
|
|
3088
|
+
return '';
|
|
3089
|
+
if (parts.length === 1) {
|
|
3090
|
+
const p = parts[0];
|
|
3091
|
+
return p.length > 1 ? p.substring(0, 2).toUpperCase() : p.toUpperCase();
|
|
3092
|
+
}
|
|
3093
|
+
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
|
|
3094
|
+
}, ...(ngDevMode ? [{ debugName: "initials" }] : []));
|
|
3095
|
+
/** Computed style for host (border and gap) */
|
|
3096
|
+
hostStyle = computed(() => {
|
|
3097
|
+
const styles = {};
|
|
3098
|
+
if (!this.inactive() && this.showBorder()) {
|
|
3099
|
+
styles.border = `2px solid ${this.borderColor()}`;
|
|
3100
|
+
styles.padding = '2px'; // Gap between border and circle
|
|
3101
|
+
styles.borderRadius = '50%'; // Ensure border is circular
|
|
3102
|
+
}
|
|
3103
|
+
return styles;
|
|
3104
|
+
}, ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
|
|
3105
|
+
/** Computed style for inner element (background and text color) */
|
|
3106
|
+
innerStyle = computed(() => {
|
|
3107
|
+
const styles = {};
|
|
3108
|
+
// Background color logic
|
|
3109
|
+
const bgColor = this.color();
|
|
3110
|
+
if (bgColor) {
|
|
3111
|
+
// Use slightly darker variant for background as requested
|
|
3112
|
+
const darkBg = getHoverColor(bgColor);
|
|
3113
|
+
styles.backgroundColor = darkBg;
|
|
3114
|
+
styles.color = getTextColor(darkBg);
|
|
3115
|
+
}
|
|
3116
|
+
return styles;
|
|
3117
|
+
}, ...(ngDevMode ? [{ debugName: "innerStyle" }] : []));
|
|
3118
|
+
onImageError() {
|
|
3119
|
+
this.imageError = true;
|
|
3120
|
+
}
|
|
3121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Avatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3122
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: Avatar, isStandalone: true, selector: "mglon-avatar", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, showBorder: { classPropertyName: "showBorder", publicName: "showBorder", isSignal: true, isRequired: false, transformFunction: null }, borderColor: { classPropertyName: "borderColor", publicName: "borderColor", isSignal: true, isRequired: false, transformFunction: null }, inactive: { classPropertyName: "inactive", publicName: "inactive", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-size": "size()", "attr.data-inactive": "inactive()", "style": "hostStyle()" } }, ngImport: i0, template: "@if (src() && !imageError) {\n<img [src]=\"src()\" [alt]=\"alt()\" [ngStyle]=\"innerStyle()\" (error)=\"onImageError()\" class=\"avatar-image\" />\n} @else if (initials()) {\n<div class=\"avatar-initials\" [ngStyle]=\"innerStyle()\">\n {{ initials() }}\n</div>\n} @else {\n<div class=\"avatar-placeholder\" [ngStyle]=\"innerStyle()\">\n <span class=\"avatar-icon\">\uD83D\uDC64</span>\n</div>\n}", styles: [":host{display:inline-block;border-radius:50%;box-sizing:border-box}:host[data-size=xs]{width:24px;height:24px}:host[data-size=xs] .avatar-icon,:host[data-size=xs] .avatar-initials{font-size:10px}:host[data-size=sm]{width:32px;height:32px}:host[data-size=sm] .avatar-icon,:host[data-size=sm] .avatar-initials{font-size:12px}:host[data-size=md]{width:40px;height:40px}:host[data-size=md] .avatar-icon,:host[data-size=md] .avatar-initials{font-size:14px}:host[data-size=lg]{width:48px;height:48px}:host[data-size=lg] .avatar-icon,:host[data-size=lg] .avatar-initials{font-size:16px}:host[data-size=xl]{width:64px;height:64px}:host[data-size=xl] .avatar-icon,:host[data-size=xl] .avatar-initials{font-size:20px}:host[data-inactive=true] .avatar-image,:host[data-inactive=true] .avatar-placeholder,:host[data-inactive=true] .avatar-initials{filter:grayscale(100%);opacity:.6}.avatar-image,.avatar-placeholder,.avatar-initials{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}.avatar-placeholder,.avatar-initials{background-color:var(--mglon-schedule-surface, #f0f0f0);display:flex;align-items:center;justify-content:center;color:var(--mglon-schedule-text-secondary, #666);-webkit-user-select:none;user-select:none;font-weight:600;text-transform:uppercase}.avatar-icon{line-height:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
3123
|
+
}
|
|
3124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Avatar, decorators: [{
|
|
3125
|
+
type: Component,
|
|
3126
|
+
args: [{ selector: 'mglon-avatar', standalone: true, imports: [CommonModule], host: {
|
|
3127
|
+
'[attr.data-size]': 'size()',
|
|
3128
|
+
'[attr.data-inactive]': 'inactive()',
|
|
3129
|
+
'[style]': 'hostStyle()'
|
|
3130
|
+
}, template: "@if (src() && !imageError) {\n<img [src]=\"src()\" [alt]=\"alt()\" [ngStyle]=\"innerStyle()\" (error)=\"onImageError()\" class=\"avatar-image\" />\n} @else if (initials()) {\n<div class=\"avatar-initials\" [ngStyle]=\"innerStyle()\">\n {{ initials() }}\n</div>\n} @else {\n<div class=\"avatar-placeholder\" [ngStyle]=\"innerStyle()\">\n <span class=\"avatar-icon\">\uD83D\uDC64</span>\n</div>\n}", styles: [":host{display:inline-block;border-radius:50%;box-sizing:border-box}:host[data-size=xs]{width:24px;height:24px}:host[data-size=xs] .avatar-icon,:host[data-size=xs] .avatar-initials{font-size:10px}:host[data-size=sm]{width:32px;height:32px}:host[data-size=sm] .avatar-icon,:host[data-size=sm] .avatar-initials{font-size:12px}:host[data-size=md]{width:40px;height:40px}:host[data-size=md] .avatar-icon,:host[data-size=md] .avatar-initials{font-size:14px}:host[data-size=lg]{width:48px;height:48px}:host[data-size=lg] .avatar-icon,:host[data-size=lg] .avatar-initials{font-size:16px}:host[data-size=xl]{width:64px;height:64px}:host[data-size=xl] .avatar-icon,:host[data-size=xl] .avatar-initials{font-size:20px}:host[data-inactive=true] .avatar-image,:host[data-inactive=true] .avatar-placeholder,:host[data-inactive=true] .avatar-initials{filter:grayscale(100%);opacity:.6}.avatar-image,.avatar-placeholder,.avatar-initials{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}.avatar-placeholder,.avatar-initials{background-color:var(--mglon-schedule-surface, #f0f0f0);display:flex;align-items:center;justify-content:center;color:var(--mglon-schedule-text-secondary, #666);-webkit-user-select:none;user-select:none;font-weight:600;text-transform:uppercase}.avatar-icon{line-height:1}\n"] }]
|
|
3131
|
+
}], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], showBorder: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBorder", required: false }] }], borderColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderColor", required: false }] }], inactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "inactive", required: false }] }] } });
|
|
3132
|
+
|
|
3133
|
+
class ResourceItemSchedule {
|
|
3134
|
+
/** Resource to display */
|
|
3135
|
+
resource = input.required(...(ngDevMode ? [{ debugName: "resource" }] : []));
|
|
3136
|
+
/** Border radius for resource item */
|
|
3137
|
+
rounded = input('sm', ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
3138
|
+
/** Density/spacing for resource item */
|
|
3139
|
+
density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
3140
|
+
/** Emitted when the resource is clicked (for toggle) */
|
|
3141
|
+
toggle = output();
|
|
3142
|
+
onToggle() {
|
|
3143
|
+
this.toggle.emit(this.resource().id);
|
|
3144
|
+
}
|
|
3145
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceItemSchedule, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3146
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: ResourceItemSchedule, isStandalone: true, selector: "mglon-resource-item-schedule", inputs: { resource: { classPropertyName: "resource", publicName: "resource", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggle: "toggle" }, host: { properties: { "attr.rounded": "rounded()", "attr.density": "density()" } }, ngImport: i0, template: "<div class=\"resource-item\" (click)=\"onToggle()\">\n <mglon-avatar [src]=\"resource().avatar || ''\" [name]=\"resource().name\" [color]=\"resource().color || ''\"\n [alt]=\"resource().name\" size=\"xs\" [showBorder]=\"true\" [borderColor]=\"resource().color || 'var(--ngx-cal-primary)'\"\n [inactive]=\"resource().isActive === false\" />\n <span class=\"resource-name\" [class.inactive]=\"resource().isActive === false\">{{ resource().name }}</span>\n</div>", styles: [".resource-item{display:flex;align-items:center;gap:var(--mglon-resource-item-gap, var(--mglon-schedule-gap-sm));padding:var(--mglon-resource-item-padding-y, var(--mglon-schedule-padding-sm)) var(--mglon-resource-item-padding-x, var(--mglon-schedule-padding-md));cursor:pointer;transition:background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing);border-radius:var(--mglon-resource-item-radius, var(--mglon-schedule-radius-sm))}.resource-item:hover{background-color:var(--mglon-resource-item-hover-bg, var(--mglon-schedule-hover-bg))}.resource-item:active{background-color:var(--mglon-resource-item-active-bg, rgba(0, 0, 0, .08))}.resource-name{font-size:var(--mglon-resource-item-font-size, var(--mglon-schedule-font-size-sm));color:var(--mglon-resource-item-text-color, var(--mglon-schedule-text-primary));font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.resource-name.inactive{color:var(--mglon-resource-item-inactive-color, var(--mglon-schedule-text-secondary));opacity:var(--mglon-resource-item-inactive-opacity, .6)}:host([rounded=none]) .resource-item{border-radius:0}:host([rounded=sm]) .resource-item{border-radius:var(--mglon-schedule-radius-sm)}:host([rounded=md]) .resource-item{border-radius:var(--mglon-schedule-radius-md)}:host([rounded=lg]) .resource-item{border-radius:var(--mglon-schedule-radius-lg)}:host([rounded=full]) .resource-item{border-radius:var(--mglon-schedule-radius-full)}:host([density=compact]) .resource-item{padding:var(--mglon-resource-item-padding-compact-y, var(--mglon-schedule-padding-xs)) var(--mglon-resource-item-padding-compact-x, var(--mglon-schedule-padding-sm));gap:var(--mglon-resource-item-gap-compact, var(--mglon-schedule-gap-xs))}:host([density=comfortable]) .resource-item{padding:var(--mglon-resource-item-padding-comfortable-y, var(--mglon-schedule-padding-sm)) var(--mglon-resource-item-padding-comfortable-x, var(--mglon-schedule-padding-md));gap:var(--mglon-resource-item-gap-comfortable, var(--mglon-schedule-gap-sm))}\n"], dependencies: [{ kind: "component", type: Avatar, selector: "mglon-avatar", inputs: ["src", "name", "color", "alt", "size", "showBorder", "borderColor", "inactive"] }] });
|
|
3147
|
+
}
|
|
3148
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceItemSchedule, decorators: [{
|
|
3149
|
+
type: Component,
|
|
3150
|
+
args: [{ selector: 'mglon-resource-item-schedule', standalone: true, imports: [Avatar], host: {
|
|
3151
|
+
'[attr.rounded]': 'rounded()',
|
|
3152
|
+
'[attr.density]': 'density()'
|
|
3153
|
+
}, template: "<div class=\"resource-item\" (click)=\"onToggle()\">\n <mglon-avatar [src]=\"resource().avatar || ''\" [name]=\"resource().name\" [color]=\"resource().color || ''\"\n [alt]=\"resource().name\" size=\"xs\" [showBorder]=\"true\" [borderColor]=\"resource().color || 'var(--ngx-cal-primary)'\"\n [inactive]=\"resource().isActive === false\" />\n <span class=\"resource-name\" [class.inactive]=\"resource().isActive === false\">{{ resource().name }}</span>\n</div>", styles: [".resource-item{display:flex;align-items:center;gap:var(--mglon-resource-item-gap, var(--mglon-schedule-gap-sm));padding:var(--mglon-resource-item-padding-y, var(--mglon-schedule-padding-sm)) var(--mglon-resource-item-padding-x, var(--mglon-schedule-padding-md));cursor:pointer;transition:background-color var(--mglon-schedule-transition-duration) var(--mglon-schedule-transition-easing);border-radius:var(--mglon-resource-item-radius, var(--mglon-schedule-radius-sm))}.resource-item:hover{background-color:var(--mglon-resource-item-hover-bg, var(--mglon-schedule-hover-bg))}.resource-item:active{background-color:var(--mglon-resource-item-active-bg, rgba(0, 0, 0, .08))}.resource-name{font-size:var(--mglon-resource-item-font-size, var(--mglon-schedule-font-size-sm));color:var(--mglon-resource-item-text-color, var(--mglon-schedule-text-primary));font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.resource-name.inactive{color:var(--mglon-resource-item-inactive-color, var(--mglon-schedule-text-secondary));opacity:var(--mglon-resource-item-inactive-opacity, .6)}:host([rounded=none]) .resource-item{border-radius:0}:host([rounded=sm]) .resource-item{border-radius:var(--mglon-schedule-radius-sm)}:host([rounded=md]) .resource-item{border-radius:var(--mglon-schedule-radius-md)}:host([rounded=lg]) .resource-item{border-radius:var(--mglon-schedule-radius-lg)}:host([rounded=full]) .resource-item{border-radius:var(--mglon-schedule-radius-full)}:host([density=compact]) .resource-item{padding:var(--mglon-resource-item-padding-compact-y, var(--mglon-schedule-padding-xs)) var(--mglon-resource-item-padding-compact-x, var(--mglon-schedule-padding-sm));gap:var(--mglon-resource-item-gap-compact, var(--mglon-schedule-gap-xs))}:host([density=comfortable]) .resource-item{padding:var(--mglon-resource-item-padding-comfortable-y, var(--mglon-schedule-padding-sm)) var(--mglon-resource-item-padding-comfortable-x, var(--mglon-schedule-padding-md));gap:var(--mglon-resource-item-gap-comfortable, var(--mglon-schedule-gap-sm))}\n"] }]
|
|
3154
|
+
}], propDecorators: { resource: [{ type: i0.Input, args: [{ isSignal: true, alias: "resource", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], toggle: [{ type: i0.Output, args: ["toggle"] }] } });
|
|
3155
|
+
|
|
3156
|
+
class ResourceListSchedule {
|
|
3157
|
+
calendarStore = inject(CalendarStore);
|
|
3158
|
+
// Sidebar UI Configuration from Store
|
|
3159
|
+
resourceItemRounded = computed(() => this.calendarStore.uiConfig().sidebar.resourceItems.rounded, ...(ngDevMode ? [{ debugName: "resourceItemRounded" }] : []));
|
|
3160
|
+
resourceItemDensity = computed(() => this.calendarStore.uiConfig().sidebar.resourceItems.density, ...(ngDevMode ? [{ debugName: "resourceItemDensity" }] : []));
|
|
3161
|
+
/** Array of resources to display */
|
|
3162
|
+
resources = input([], ...(ngDevMode ? [{ debugName: "resources" }] : []));
|
|
3163
|
+
/** Emitted when a resource is toggled */
|
|
3164
|
+
toggleResource = output();
|
|
3165
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceListSchedule, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3166
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ResourceListSchedule, isStandalone: true, selector: "mglon-resource-list-schedule", inputs: { resources: { classPropertyName: "resources", publicName: "resources", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleResource: "toggleResource" }, ngImport: i0, template: "<div class=\"resource-list\">\n @if (resources().length > 0) {\n @for (resource of resources(); track resource.id) {\n <mglon-resource-item-schedule [resource]=\"resource\" [rounded]=\"resourceItemRounded()\"\n [density]=\"resourceItemDensity()\" (toggle)=\"toggleResource.emit($event)\" />\n }\n } @else {\n <div class=\"resource-list-empty\">\n <span class=\"empty-icon\">\uD83D\uDCCB</span>\n <span class=\"empty-text\">No resources available</span>\n </div>\n }\n</div>", styles: [".resource-list{display:flex;flex-direction:column;gap:var(--mglon-resource-list-gap, var(--mglon-schedule-gap-xs));overflow-y:auto;max-height:100%}.resource-list-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--mglon-resource-list-empty-padding, var(--mglon-schedule-padding-lg));gap:var(--mglon-resource-list-empty-gap, var(--mglon-schedule-gap-sm));color:var(--mglon-resource-list-empty-color, var(--mglon-schedule-text-secondary));text-align:center}.empty-icon{font-size:var(--mglon-resource-list-empty-icon-size, 2rem);opacity:var(--mglon-resource-list-empty-icon-opacity, .3)}.empty-text{font-size:var(--mglon-resource-list-empty-text-size, var(--mglon-schedule-font-size-sm))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ResourceItemSchedule, selector: "mglon-resource-item-schedule", inputs: ["resource", "rounded", "density"], outputs: ["toggle"] }] });
|
|
3167
|
+
}
|
|
3168
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceListSchedule, decorators: [{
|
|
3169
|
+
type: Component,
|
|
3170
|
+
args: [{ selector: 'mglon-resource-list-schedule', standalone: true, imports: [CommonModule, ResourceItemSchedule], template: "<div class=\"resource-list\">\n @if (resources().length > 0) {\n @for (resource of resources(); track resource.id) {\n <mglon-resource-item-schedule [resource]=\"resource\" [rounded]=\"resourceItemRounded()\"\n [density]=\"resourceItemDensity()\" (toggle)=\"toggleResource.emit($event)\" />\n }\n } @else {\n <div class=\"resource-list-empty\">\n <span class=\"empty-icon\">\uD83D\uDCCB</span>\n <span class=\"empty-text\">No resources available</span>\n </div>\n }\n</div>", styles: [".resource-list{display:flex;flex-direction:column;gap:var(--mglon-resource-list-gap, var(--mglon-schedule-gap-xs));overflow-y:auto;max-height:100%}.resource-list-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--mglon-resource-list-empty-padding, var(--mglon-schedule-padding-lg));gap:var(--mglon-resource-list-empty-gap, var(--mglon-schedule-gap-sm));color:var(--mglon-resource-list-empty-color, var(--mglon-schedule-text-secondary));text-align:center}.empty-icon{font-size:var(--mglon-resource-list-empty-icon-size, 2rem);opacity:var(--mglon-resource-list-empty-icon-opacity, .3)}.empty-text{font-size:var(--mglon-resource-list-empty-text-size, var(--mglon-schedule-font-size-sm))}\n"] }]
|
|
3171
|
+
}], propDecorators: { resources: [{ type: i0.Input, args: [{ isSignal: true, alias: "resources", required: false }] }], toggleResource: [{ type: i0.Output, args: ["toggleResource"] }] } });
|
|
3172
|
+
|
|
3173
|
+
class SidebarSchedule {
|
|
3174
|
+
store = inject(CalendarStore);
|
|
3175
|
+
backgroundColor = computed(() => this.store.uiConfig().sidebar.background, ...(ngDevMode ? [{ debugName: "backgroundColor" }] : []));
|
|
3176
|
+
borderRadius = computed(() => this.store.uiConfig().sidebar.rounded || 'none', ...(ngDevMode ? [{ debugName: "borderRadius" }] : []));
|
|
3177
|
+
onToggleResource(id) {
|
|
3178
|
+
this.store.toggleResource(id);
|
|
3179
|
+
}
|
|
3180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SidebarSchedule, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3181
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: SidebarSchedule, isStandalone: true, selector: "mglon-sidebar-schedule", host: { properties: { "style.--mglon-sidebar-radius": "borderRadius() === \"none\" ? \"0\" : \"var(--mglon-schedule-radius-\" + borderRadius() + \")\"", "style.--mglon-sidebar-bg": "backgroundColor()", "attr.data-radius": "borderRadius()" } }, ngImport: i0, template: "<aside class=\"sidebar-schedule\">\n <header class=\"sidebar-header\">\n <h3 class=\"sidebar-title\">Resources</h3>\n </header>\n <div class=\"sidebar-content\">\n <mglon-resource-list-schedule [resources]=\"store.allResources()\" (toggleResource)=\"onToggleResource($event)\" />\n </div>\n</aside>", styles: [".sidebar-schedule{display:flex;flex-direction:column;height:100%;width:250px;background-color:var(--mglon-sidebar-bg, var(--mglon-schedule-surface));border-right:1px solid var(--mglon-schedule-border);border-radius:var(--mglon-sidebar-radius, var(--mglon-schedule-radius-sm));overflow:hidden}.sidebar-header{padding:var(--mglon-schedule-spacing-md, 12px) var(--mglon-schedule-spacing-md, 12px);background-color:var(--mglon-schedule-background)}.sidebar-title{margin:0;font-size:1rem;font-weight:600;color:var(--mglon-schedule-text-primary)}.sidebar-content{flex:1;overflow-y:auto;padding:var(--mglon-schedule-spacing-sm, 8px)}\n"], dependencies: [{ kind: "component", type: ResourceListSchedule, selector: "mglon-resource-list-schedule", inputs: ["resources"], outputs: ["toggleResource"] }] });
|
|
3182
|
+
}
|
|
3183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SidebarSchedule, decorators: [{
|
|
3184
|
+
type: Component,
|
|
3185
|
+
args: [{ selector: 'mglon-sidebar-schedule', standalone: true, imports: [ResourceListSchedule], host: {
|
|
3186
|
+
'[style.--mglon-sidebar-radius]': 'borderRadius() === "none" ? "0" : "var(--mglon-schedule-radius-" + borderRadius() + ")"',
|
|
3187
|
+
'[style.--mglon-sidebar-bg]': 'backgroundColor()',
|
|
3188
|
+
'[attr.data-radius]': 'borderRadius()'
|
|
3189
|
+
}, template: "<aside class=\"sidebar-schedule\">\n <header class=\"sidebar-header\">\n <h3 class=\"sidebar-title\">Resources</h3>\n </header>\n <div class=\"sidebar-content\">\n <mglon-resource-list-schedule [resources]=\"store.allResources()\" (toggleResource)=\"onToggleResource($event)\" />\n </div>\n</aside>", styles: [".sidebar-schedule{display:flex;flex-direction:column;height:100%;width:250px;background-color:var(--mglon-sidebar-bg, var(--mglon-schedule-surface));border-right:1px solid var(--mglon-schedule-border);border-radius:var(--mglon-sidebar-radius, var(--mglon-schedule-radius-sm));overflow:hidden}.sidebar-header{padding:var(--mglon-schedule-spacing-md, 12px) var(--mglon-schedule-spacing-md, 12px);background-color:var(--mglon-schedule-background)}.sidebar-title{margin:0;font-size:1rem;font-weight:600;color:var(--mglon-schedule-text-primary)}.sidebar-content{flex:1;overflow-y:auto;padding:var(--mglon-schedule-spacing-sm, 8px)}\n"] }]
|
|
3190
|
+
}] });
|
|
3191
|
+
|
|
3192
|
+
class Schedule {
|
|
3193
|
+
config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
3194
|
+
// ============================================
|
|
3195
|
+
// UI CONFIGURATION INPUTS (Area-Based)
|
|
3196
|
+
// ============================================
|
|
3197
|
+
/** Optional UI configuration for header area (button-group, navigation, today button) */
|
|
3198
|
+
headerUI = input(...(ngDevMode ? [undefined, { debugName: "headerUI" }] : []));
|
|
3199
|
+
/** Optional UI configuration for sidebar area (resource items) */
|
|
3200
|
+
sidebarUI = input(...(ngDevMode ? [undefined, { debugName: "sidebarUI" }] : []));
|
|
3201
|
+
/** Optional UI configuration for grid area (event slots, overflow indicators) */
|
|
3202
|
+
gridUI = input(...(ngDevMode ? [undefined, { debugName: "gridUI" }] : []));
|
|
3203
|
+
/** Optional default color for all events in the schedule */
|
|
3204
|
+
color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : []));
|
|
3205
|
+
/**
|
|
3206
|
+
* Whether to automatically generate vivid/pastel variants for events.
|
|
3207
|
+
* If true, regular events use vivid colors and recurrent events use pastel variants.
|
|
3208
|
+
* If false, colors are used exactly as provided.
|
|
3209
|
+
* @default true
|
|
3210
|
+
*/
|
|
3211
|
+
useDynamicColors = input(true, ...(ngDevMode ? [{ debugName: "useDynamicColors" }] : []));
|
|
3212
|
+
add = output();
|
|
3213
|
+
// ============================================
|
|
3214
|
+
// OUTPUT EVENTS
|
|
3215
|
+
// ============================================
|
|
3216
|
+
/** Emitted when selection starts (mousedown) */
|
|
3217
|
+
selectionStart = output();
|
|
3218
|
+
/** Emitted while selecting (mousemove) */
|
|
3219
|
+
selectionChange = output();
|
|
3220
|
+
/** Emitted when selection ends (mouseup) */
|
|
3221
|
+
selectionEnd = output();
|
|
3222
|
+
/** Emitted when view mode changes */
|
|
3223
|
+
viewChange = output();
|
|
3224
|
+
/** Emitted when displayed date range changes */
|
|
3225
|
+
dateRangeChange = output();
|
|
3226
|
+
/** Emitted when a resource is shown/activated */
|
|
3227
|
+
resourceShow = output();
|
|
3228
|
+
/** Emitted when a resource is hidden/deactivated */
|
|
3229
|
+
resourceHide = output();
|
|
3230
|
+
store = inject(CalendarStore);
|
|
3231
|
+
constructor() {
|
|
3232
|
+
// Register global handlers
|
|
3233
|
+
// this.eventStore.setGlobalHandlers({
|
|
3234
|
+
// eventClick: this.eventClick
|
|
3235
|
+
// });
|
|
3236
|
+
effect(() => {
|
|
3237
|
+
const conf = this.config();
|
|
3238
|
+
if (conf) {
|
|
3239
|
+
this.store.updateConfig(conf);
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
// Handle UI configuration updates (area-based)
|
|
3243
|
+
effect(() => {
|
|
3244
|
+
const headerConfig = this.headerUI();
|
|
3245
|
+
const sidebarConfig = this.sidebarUI();
|
|
3246
|
+
const gridConfig = this.gridUI();
|
|
3247
|
+
const primaryColor = this.color();
|
|
3248
|
+
const useDynamicColors = this.useDynamicColors();
|
|
3249
|
+
// Only update if at least one config is provided
|
|
3250
|
+
if (headerConfig || sidebarConfig || gridConfig || primaryColor || useDynamicColors !== undefined) {
|
|
3251
|
+
// Construct a safe partial grid config
|
|
3252
|
+
const finalGridConfig = gridConfig ? { ...gridConfig } : {};
|
|
3253
|
+
if (primaryColor) {
|
|
3254
|
+
finalGridConfig.eventSlots = {
|
|
3255
|
+
...finalGridConfig.eventSlots,
|
|
3256
|
+
color: finalGridConfig.eventSlots?.color || primaryColor
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
if (useDynamicColors !== undefined) {
|
|
3260
|
+
finalGridConfig.useDynamicColors = useDynamicColors;
|
|
3261
|
+
}
|
|
3262
|
+
this.store.setUIConfig({
|
|
3263
|
+
header: headerConfig,
|
|
3264
|
+
sidebar: sidebarConfig,
|
|
3265
|
+
grid: finalGridConfig
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
// Emit viewChange when view mode changes
|
|
3270
|
+
effect(() => {
|
|
3271
|
+
const view = this.store.viewMode();
|
|
3272
|
+
this.viewChange.emit(view);
|
|
3273
|
+
});
|
|
3274
|
+
// Emit resource show/hide events when resources change
|
|
3275
|
+
effect(() => {
|
|
3276
|
+
const resources = this.store.allResources();
|
|
3277
|
+
resources.forEach(resource => {
|
|
3278
|
+
// Check previous state vs current state
|
|
3279
|
+
const wasActive = this.previousResourceStates.get(resource.id);
|
|
3280
|
+
const isActive = resource.isActive !== false;
|
|
3281
|
+
if (wasActive !== undefined && wasActive !== isActive) {
|
|
3282
|
+
if (isActive) {
|
|
3283
|
+
this.resourceShow.emit(resource.id);
|
|
3284
|
+
}
|
|
3285
|
+
else {
|
|
3286
|
+
this.resourceHide.emit(resource.id);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
// Update previous state
|
|
3290
|
+
this.previousResourceStates.set(resource.id, isActive);
|
|
3291
|
+
});
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
previousResourceStates = new Map();
|
|
3295
|
+
get api() {
|
|
3296
|
+
return {
|
|
3297
|
+
next: () => this.store.next(),
|
|
3298
|
+
prev: () => this.store.prev(),
|
|
3299
|
+
today: () => this.store.today(),
|
|
3300
|
+
changeView: (view) => this.store.changeView(view),
|
|
3301
|
+
setDate: (date) => this.store.setDate(date)
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Schedule, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3305
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: Schedule, isStandalone: true, selector: "mglon-schedule", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, headerUI: { classPropertyName: "headerUI", publicName: "headerUI", isSignal: true, isRequired: false, transformFunction: null }, sidebarUI: { classPropertyName: "sidebarUI", publicName: "sidebarUI", isSignal: true, isRequired: false, transformFunction: null }, gridUI: { classPropertyName: "gridUI", publicName: "gridUI", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, useDynamicColors: { classPropertyName: "useDynamicColors", publicName: "useDynamicColors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { add: "add", selectionStart: "selectionStart", selectionChange: "selectionChange", selectionEnd: "selectionEnd", viewChange: "viewChange", dateRangeChange: "dateRangeChange", resourceShow: "resourceShow", resourceHide: "resourceHide" }, providers: [CalendarStore], ngImport: i0, template: "<div class=\"ng-sched-schedule\">\n <ng-content />\n <!-- Header -->\n <mglon-header-schedule [title]=\"store.formattedDate()\" [activeView]=\"store.viewMode()\" [views]=\"store.config().views!\"\n [editable]=\"store.config().editable!\" (next)=\"store.next()\" (prev)=\"store.prev()\" (today)=\"store.today()\"\n (viewChange)=\"store.changeView($event)\" (add)=\"add.emit()\">\n </mglon-header-schedule>\n\n <!-- Body -->\n\n <main class=\"ng-sched-body\">\n @if (store.config().showSidebar) {\n <div class=\"ng-sched-body__sidebar\">\n <mglon-sidebar-schedule></mglon-sidebar-schedule>\n </div>\n }\n <div class=\"ng-sched-body__content\">\n @switch (store.viewMode()) {\n @case ('resource') {\n <mglon-resource-view></mglon-resource-view>\n }\n @case ('month') {\n <mglon-month-view></mglon-month-view>\n }\n @case ('week') {\n <mglon-week-view (selectionStart)=\"selectionStart.emit($event)\" (selectionChange)=\"selectionChange.emit($event)\"\n (selectionEnd)=\"selectionEnd.emit($event)\">\n </mglon-week-view>\n }\n @default {\n <div class=\"ng-sched-placeholder\">\n View '{{ store.viewMode() }}' not implemented yet.\n </div>\n }\n }\n </div>\n\n </main>\n</div>", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;width:100%;--mglon-schedule-primary-50: #e8f0fe;--mglon-schedule-primary-100: #d2e3fc;--mglon-schedule-primary-200: #a7cbfb;--mglon-schedule-primary-300: #7cacf8;--mglon-schedule-primary-400: #518ef5;--mglon-schedule-primary-500: #1a73e8;--mglon-schedule-primary-600: #1967d2;--mglon-schedule-primary-700: #185abc;--mglon-schedule-primary-800: #174ea6;--mglon-schedule-primary-900: #0d3a86;--mglon-schedule-primary-950: #08265a;--mglon-schedule-neutral-50: #fafafa;--mglon-schedule-neutral-100: #f5f5f5;--mglon-schedule-neutral-200: #e5e5e5;--mglon-schedule-neutral-300: #d4d4d4;--mglon-schedule-neutral-400: #a3a3a3;--mglon-schedule-neutral-500: #737373;--mglon-schedule-neutral-600: #525252;--mglon-schedule-neutral-700: #404040;--mglon-schedule-neutral-800: #262626;--mglon-schedule-neutral-900: #171717;--mglon-schedule-neutral-950: #0a0a0a;--mglon-schedule-primary: var(--mglon-schedule-primary-500);--mglon-schedule-on-primary: #ffffff;--mglon-schedule-surface: #ffffff;--mglon-schedule-on-surface: var(--mglon-schedule-neutral-900);--mglon-schedule-surface-variant: var(--mglon-schedule-neutral-100);--mglon-schedule-on-surface-variant: var(--mglon-schedule-neutral-700);--mglon-schedule-background: #ffffff;--mglon-schedule-on-background: var(--mglon-schedule-neutral-900);--mglon-schedule-border: var(--mglon-schedule-neutral-200);--mglon-schedule-text-primary: var(--mglon-schedule-neutral-900);--mglon-schedule-text-secondary: var(--mglon-schedule-neutral-500);--mglon-schedule-text-tertiary: var(--mglon-schedule-neutral-400);--mglon-schedule-hover: var(--mglon-schedule-neutral-100);--mglon-schedule-selection: var(--mglon-schedule-primary-50);--mglon-schedule-error: #ea4335;--mglon-schedule-on-error: #ffffff;--mglon-schedule-spacing-xs: 4px;--mglon-schedule-spacing-sm: 8px;--mglon-schedule-spacing-md: 16px;--mglon-schedule-spacing-lg: 24px;--mglon-schedule-padding-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-padding-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-padding-md: var(--mglon-schedule-spacing-md);--mglon-schedule-padding-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-margin-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-margin-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-margin-md: var(--mglon-schedule-spacing-md);--mglon-schedule-margin-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-gap-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-gap-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-gap-md: var(--mglon-schedule-spacing-md);--mglon-schedule-gap-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-border-width-sm: 1px;--mglon-schedule-border-width-md: 2px;--mglon-schedule-border-width-lg: 4px;--mglon-schedule-header-height: 48px;--mglon-schedule-sidebar-width: 60px;--mglon-schedule-cell-height: 48px;--mglon-schedule-radius-sm: 4px;--mglon-schedule-radius-md: 8px;--mglon-schedule-radius-lg: 16px;--mglon-schedule-radius-full: 9999px;--mglon-schedule-font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;--mglon-schedule-font-size-xs: 10px;--mglon-schedule-font-size-sm: 12px;--mglon-schedule-font-size-md: 14px;--mglon-schedule-font-size-lg: 16px;--mglon-schedule-shadow-sm: 0 1px 2px rgba(0, 0, 0, .1);--mglon-schedule-shadow-md: 0 2px 4px rgba(0, 0, 0, .15);--mglon-schedule-shadow-lg: 0 4px 8px rgba(0, 0, 0, .2);--mglon-schedule-z-sticky: 10;--mglon-schedule-z-header: 20;--mglon-schedule-hover-bg: rgba(0, 0, 0, .04);--mglon-schedule-focus-ring-color: var(--mglon-schedule-primary-200);--mglon-schedule-disabled-opacity: .5;--mglon-schedule-transition-duration: .2s;--mglon-schedule-transition-easing: cubic-bezier(.4, 0, .2, 1);--mglon-schedule-month-week-display: grid;--mglon-schedule-month-week-columns: repeat(7, 1fr);--mglon-schedule-month-week-min-height: 80px;--mglon-schedule-month-week-border-bottom-width: 1px}.ng-sched-schedule{display:grid;grid-template-rows:auto 1fr;height:100%;width:100%;background-color:var(--mglon-schedule-background);color:var(--mglon-schedule-on-background);font-family:var(--mglon-schedule-font-family)}.ng-sched-header{display:flex;align-items:center;justify-content:space-between;padding:var(--mglon-schedule-spacing-sm) var(--mglon-schedule-spacing-md);border-bottom:1px solid var(--mglon-schedule-border);background-color:var(--mglon-schedule-surface)}.ng-sched-header__nav{display:flex;align-items:center;gap:var(--mglon-schedule-spacing-sm)}.ng-sched-header__title{margin:0 0 0 var(--mglon-schedule-spacing-md);font-size:1.25rem;font-weight:500}.ng-sched-header__views{display:flex;gap:0;border:1px solid var(--mglon-schedule-border);border-radius:4px;overflow:hidden}.ng-sched-button{padding:6px 12px;border:1px solid var(--mglon-schedule-border);background-color:transparent;color:var(--mglon-schedule-on-surface);font-family:inherit;font-size:.875rem;cursor:pointer;transition:all .2s ease-in-out;border-radius:4px;outline:none;-webkit-user-select:none;user-select:none}.ng-sched-button--icon{padding:6px 10px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.ng-sched-button:hover:not(:disabled){background-color:var(--mglon-schedule-hover)}.ng-sched-button:active:not(:disabled){transform:scale(.98)}.ng-sched-button:focus-visible{outline:2px solid var(--mglon-schedule-primary);outline-offset:2px}.ng-sched-button--active{background-color:var(--mglon-schedule-selection);color:var(--mglon-schedule-primary);font-weight:500}.ng-sched-button:disabled{opacity:.5;cursor:not-allowed}.ng-sched-header__views .ng-sched-button{border:none;border-right:1px solid var(--mglon-schedule-border);border-radius:0}.ng-sched-header__views .ng-sched-button:last-child{border-right:none}.ng-sched-body{position:relative;height:100%;overflow:hidden;display:grid;grid-template-columns:auto 1fr;gap:0}.ng-sched-body:has(>:only-child){grid-template-columns:1fr}.ng-sched-body>:last-child{overflow:auto;min-width:0}.ng-sched-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:var(--mglon-schedule-spacing-lg);color:var(--mglon-schedule-text-secondary);font-size:1rem;text-align:center;gap:var(--mglon-schedule-spacing-md)}.ng-sched-placeholder:before{content:\"\\1f4c5\";font-size:3rem;opacity:.3}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ResourceView, selector: "mglon-resource-view" }, { kind: "component", type: MonthView, selector: "mglon-month-view" }, { kind: "component", type: HeaderSchedule, selector: "mglon-header-schedule", inputs: ["title", "activeView", "views", "editable"], outputs: ["next", "prev", "today", "viewChange", "add"] }, { kind: "component", type: SidebarSchedule, selector: "mglon-sidebar-schedule" }, { kind: "component", type: WeekView, selector: "mglon-week-view", outputs: ["selectionStart", "selectionChange", "selectionEnd"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
3306
|
+
}
|
|
3307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Schedule, decorators: [{
|
|
3308
|
+
type: Component,
|
|
3309
|
+
args: [{ selector: 'mglon-schedule', standalone: true, imports: [CommonModule, ResourceView, MonthView, HeaderSchedule, SidebarSchedule, WeekView], providers: [CalendarStore], encapsulation: ViewEncapsulation.None, template: "<div class=\"ng-sched-schedule\">\n <ng-content />\n <!-- Header -->\n <mglon-header-schedule [title]=\"store.formattedDate()\" [activeView]=\"store.viewMode()\" [views]=\"store.config().views!\"\n [editable]=\"store.config().editable!\" (next)=\"store.next()\" (prev)=\"store.prev()\" (today)=\"store.today()\"\n (viewChange)=\"store.changeView($event)\" (add)=\"add.emit()\">\n </mglon-header-schedule>\n\n <!-- Body -->\n\n <main class=\"ng-sched-body\">\n @if (store.config().showSidebar) {\n <div class=\"ng-sched-body__sidebar\">\n <mglon-sidebar-schedule></mglon-sidebar-schedule>\n </div>\n }\n <div class=\"ng-sched-body__content\">\n @switch (store.viewMode()) {\n @case ('resource') {\n <mglon-resource-view></mglon-resource-view>\n }\n @case ('month') {\n <mglon-month-view></mglon-month-view>\n }\n @case ('week') {\n <mglon-week-view (selectionStart)=\"selectionStart.emit($event)\" (selectionChange)=\"selectionChange.emit($event)\"\n (selectionEnd)=\"selectionEnd.emit($event)\">\n </mglon-week-view>\n }\n @default {\n <div class=\"ng-sched-placeholder\">\n View '{{ store.viewMode() }}' not implemented yet.\n </div>\n }\n }\n </div>\n\n </main>\n</div>", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;width:100%;--mglon-schedule-primary-50: #e8f0fe;--mglon-schedule-primary-100: #d2e3fc;--mglon-schedule-primary-200: #a7cbfb;--mglon-schedule-primary-300: #7cacf8;--mglon-schedule-primary-400: #518ef5;--mglon-schedule-primary-500: #1a73e8;--mglon-schedule-primary-600: #1967d2;--mglon-schedule-primary-700: #185abc;--mglon-schedule-primary-800: #174ea6;--mglon-schedule-primary-900: #0d3a86;--mglon-schedule-primary-950: #08265a;--mglon-schedule-neutral-50: #fafafa;--mglon-schedule-neutral-100: #f5f5f5;--mglon-schedule-neutral-200: #e5e5e5;--mglon-schedule-neutral-300: #d4d4d4;--mglon-schedule-neutral-400: #a3a3a3;--mglon-schedule-neutral-500: #737373;--mglon-schedule-neutral-600: #525252;--mglon-schedule-neutral-700: #404040;--mglon-schedule-neutral-800: #262626;--mglon-schedule-neutral-900: #171717;--mglon-schedule-neutral-950: #0a0a0a;--mglon-schedule-primary: var(--mglon-schedule-primary-500);--mglon-schedule-on-primary: #ffffff;--mglon-schedule-surface: #ffffff;--mglon-schedule-on-surface: var(--mglon-schedule-neutral-900);--mglon-schedule-surface-variant: var(--mglon-schedule-neutral-100);--mglon-schedule-on-surface-variant: var(--mglon-schedule-neutral-700);--mglon-schedule-background: #ffffff;--mglon-schedule-on-background: var(--mglon-schedule-neutral-900);--mglon-schedule-border: var(--mglon-schedule-neutral-200);--mglon-schedule-text-primary: var(--mglon-schedule-neutral-900);--mglon-schedule-text-secondary: var(--mglon-schedule-neutral-500);--mglon-schedule-text-tertiary: var(--mglon-schedule-neutral-400);--mglon-schedule-hover: var(--mglon-schedule-neutral-100);--mglon-schedule-selection: var(--mglon-schedule-primary-50);--mglon-schedule-error: #ea4335;--mglon-schedule-on-error: #ffffff;--mglon-schedule-spacing-xs: 4px;--mglon-schedule-spacing-sm: 8px;--mglon-schedule-spacing-md: 16px;--mglon-schedule-spacing-lg: 24px;--mglon-schedule-padding-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-padding-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-padding-md: var(--mglon-schedule-spacing-md);--mglon-schedule-padding-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-margin-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-margin-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-margin-md: var(--mglon-schedule-spacing-md);--mglon-schedule-margin-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-gap-xs: var(--mglon-schedule-spacing-xs);--mglon-schedule-gap-sm: var(--mglon-schedule-spacing-sm);--mglon-schedule-gap-md: var(--mglon-schedule-spacing-md);--mglon-schedule-gap-lg: var(--mglon-schedule-spacing-lg);--mglon-schedule-border-width-sm: 1px;--mglon-schedule-border-width-md: 2px;--mglon-schedule-border-width-lg: 4px;--mglon-schedule-header-height: 48px;--mglon-schedule-sidebar-width: 60px;--mglon-schedule-cell-height: 48px;--mglon-schedule-radius-sm: 4px;--mglon-schedule-radius-md: 8px;--mglon-schedule-radius-lg: 16px;--mglon-schedule-radius-full: 9999px;--mglon-schedule-font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;--mglon-schedule-font-size-xs: 10px;--mglon-schedule-font-size-sm: 12px;--mglon-schedule-font-size-md: 14px;--mglon-schedule-font-size-lg: 16px;--mglon-schedule-shadow-sm: 0 1px 2px rgba(0, 0, 0, .1);--mglon-schedule-shadow-md: 0 2px 4px rgba(0, 0, 0, .15);--mglon-schedule-shadow-lg: 0 4px 8px rgba(0, 0, 0, .2);--mglon-schedule-z-sticky: 10;--mglon-schedule-z-header: 20;--mglon-schedule-hover-bg: rgba(0, 0, 0, .04);--mglon-schedule-focus-ring-color: var(--mglon-schedule-primary-200);--mglon-schedule-disabled-opacity: .5;--mglon-schedule-transition-duration: .2s;--mglon-schedule-transition-easing: cubic-bezier(.4, 0, .2, 1);--mglon-schedule-month-week-display: grid;--mglon-schedule-month-week-columns: repeat(7, 1fr);--mglon-schedule-month-week-min-height: 80px;--mglon-schedule-month-week-border-bottom-width: 1px}.ng-sched-schedule{display:grid;grid-template-rows:auto 1fr;height:100%;width:100%;background-color:var(--mglon-schedule-background);color:var(--mglon-schedule-on-background);font-family:var(--mglon-schedule-font-family)}.ng-sched-header{display:flex;align-items:center;justify-content:space-between;padding:var(--mglon-schedule-spacing-sm) var(--mglon-schedule-spacing-md);border-bottom:1px solid var(--mglon-schedule-border);background-color:var(--mglon-schedule-surface)}.ng-sched-header__nav{display:flex;align-items:center;gap:var(--mglon-schedule-spacing-sm)}.ng-sched-header__title{margin:0 0 0 var(--mglon-schedule-spacing-md);font-size:1.25rem;font-weight:500}.ng-sched-header__views{display:flex;gap:0;border:1px solid var(--mglon-schedule-border);border-radius:4px;overflow:hidden}.ng-sched-button{padding:6px 12px;border:1px solid var(--mglon-schedule-border);background-color:transparent;color:var(--mglon-schedule-on-surface);font-family:inherit;font-size:.875rem;cursor:pointer;transition:all .2s ease-in-out;border-radius:4px;outline:none;-webkit-user-select:none;user-select:none}.ng-sched-button--icon{padding:6px 10px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.ng-sched-button:hover:not(:disabled){background-color:var(--mglon-schedule-hover)}.ng-sched-button:active:not(:disabled){transform:scale(.98)}.ng-sched-button:focus-visible{outline:2px solid var(--mglon-schedule-primary);outline-offset:2px}.ng-sched-button--active{background-color:var(--mglon-schedule-selection);color:var(--mglon-schedule-primary);font-weight:500}.ng-sched-button:disabled{opacity:.5;cursor:not-allowed}.ng-sched-header__views .ng-sched-button{border:none;border-right:1px solid var(--mglon-schedule-border);border-radius:0}.ng-sched-header__views .ng-sched-button:last-child{border-right:none}.ng-sched-body{position:relative;height:100%;overflow:hidden;display:grid;grid-template-columns:auto 1fr;gap:0}.ng-sched-body:has(>:only-child){grid-template-columns:1fr}.ng-sched-body>:last-child{overflow:auto;min-width:0}.ng-sched-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:var(--mglon-schedule-spacing-lg);color:var(--mglon-schedule-text-secondary);font-size:1rem;text-align:center;gap:var(--mglon-schedule-spacing-md)}.ng-sched-placeholder:before{content:\"\\1f4c5\";font-size:3rem;opacity:.3}\n"] }]
|
|
3310
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], headerUI: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerUI", required: false }] }], sidebarUI: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarUI", required: false }] }], gridUI: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridUI", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], useDynamicColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "useDynamicColors", required: false }] }], add: [{ type: i0.Output, args: ["add"] }], selectionStart: [{ type: i0.Output, args: ["selectionStart"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionEnd: [{ type: i0.Output, args: ["selectionEnd"] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], dateRangeChange: [{ type: i0.Output, args: ["dateRangeChange"] }], resourceShow: [{ type: i0.Output, args: ["resourceShow"] }], resourceHide: [{ type: i0.Output, args: ["resourceHide"] }] } });
|
|
3311
|
+
|
|
3312
|
+
/**
|
|
3313
|
+
* Injection token for providing resource ID to child components
|
|
3314
|
+
*/
|
|
3315
|
+
const RESOURCE_ID_TOKEN = new InjectionToken('RESOURCE_ID');
|
|
3316
|
+
/**
|
|
3317
|
+
* ResourceComponent - Declarative component representing a calendar resource
|
|
3318
|
+
*
|
|
3319
|
+
* Usage:
|
|
3320
|
+
* <mglon-resource
|
|
3321
|
+
* id="room-1"
|
|
3322
|
+
* name="Conference Room A"
|
|
3323
|
+
* [color]="'#0860c4'">
|
|
3324
|
+
* <mglon-event>...</mglon-event>
|
|
3325
|
+
* </mglon-resource>
|
|
3326
|
+
*/
|
|
3327
|
+
class ResourceEvents {
|
|
3328
|
+
/** Unique identifier for the resource */
|
|
3329
|
+
id = input.required(...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
3330
|
+
/** Display name of the resource */
|
|
3331
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
3332
|
+
/** Color for the resource (border/background) */
|
|
3333
|
+
color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : []));
|
|
3334
|
+
/** URL of image or initials for display */
|
|
3335
|
+
avatar = input(...(ngDevMode ? [undefined, { debugName: "avatar" }] : []));
|
|
3336
|
+
/** Additional description of the resource */
|
|
3337
|
+
description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
|
|
3338
|
+
/** Tags for filtering */
|
|
3339
|
+
tags = input(DEFAULT_RESOURCE_INPUTS.tags, ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
3340
|
+
/** If true, events for this resource cannot be edited */
|
|
3341
|
+
isReadOnly = input(DEFAULT_RESOURCE_INPUTS.isReadOnly, ...(ngDevMode ? [{ debugName: "isReadOnly" }] : []));
|
|
3342
|
+
/** If true, this resource does not accept new events */
|
|
3343
|
+
isBlocked = input(DEFAULT_RESOURCE_INPUTS.isBlocked, ...(ngDevMode ? [{ debugName: "isBlocked" }] : []));
|
|
3344
|
+
/** If true, resource is active and visible. Default: true */
|
|
3345
|
+
isActive = input(DEFAULT_RESOURCE_INPUTS.isActive, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
|
|
3346
|
+
/** Flexible user-defined data */
|
|
3347
|
+
metadata = input(...(ngDevMode ? [undefined, { debugName: "metadata" }] : []));
|
|
3348
|
+
/** If true, allows resizing events for this resource. Default: true */
|
|
3349
|
+
resizableEvents = input(DEFAULT_RESOURCE_INPUTS.resizableEvents, ...(ngDevMode ? [{ debugName: "resizableEvents" }] : []));
|
|
3350
|
+
elRef = inject(ElementRef);
|
|
3351
|
+
store = inject(CalendarStore);
|
|
3352
|
+
ngAfterContentInit() {
|
|
3353
|
+
this._removeInvalidNodes();
|
|
3354
|
+
}
|
|
3355
|
+
_removeInvalidNodes() {
|
|
3356
|
+
const children = this.elRef.nativeElement.childNodes;
|
|
3357
|
+
children.forEach((node) => {
|
|
3358
|
+
if (node.nodeType === 1 && node.tagName !== 'MGLON-EVENT') {
|
|
3359
|
+
console.warn(`[ResourceEvents] The element <${node.tagName.toLowerCase()}> is not allowed inside mglon-resource-events. Only mglon-event components are allowed. This node will be removed.`);
|
|
3360
|
+
node.remove();
|
|
3361
|
+
}
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
constructor() {
|
|
3365
|
+
// Sync isActive input changes to EventStore
|
|
3366
|
+
effect(() => {
|
|
3367
|
+
const shouldBeActive = this.isActive();
|
|
3368
|
+
const resourceId = this.id();
|
|
3369
|
+
if (resourceId) {
|
|
3370
|
+
if (shouldBeActive) {
|
|
3371
|
+
this.store.showResource(resourceId);
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
this.store.hideResource(resourceId);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
}, { allowSignalWrites: true });
|
|
3378
|
+
}
|
|
3379
|
+
ngOnInit() {
|
|
3380
|
+
this.registerResource();
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Lifecycle: Unregister resource on destroy
|
|
3384
|
+
*/
|
|
3385
|
+
ngOnDestroy() {
|
|
3386
|
+
// Only unregister if the component was fully initialized
|
|
3387
|
+
try {
|
|
3388
|
+
const resourceId = this.id();
|
|
3389
|
+
if (resourceId) {
|
|
3390
|
+
this.store.unregisterResource(resourceId);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
catch (e) {
|
|
3394
|
+
// Ignore errors during cleanup (e.g., inputs not yet initialized in tests)
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
registerResource() {
|
|
3398
|
+
const resource = {
|
|
3399
|
+
id: this.id(),
|
|
3400
|
+
name: this.name(),
|
|
3401
|
+
color: this.color(),
|
|
3402
|
+
avatar: this.avatar(),
|
|
3403
|
+
description: this.description(),
|
|
3404
|
+
tags: this.tags(),
|
|
3405
|
+
isReadOnly: this.isReadOnly(),
|
|
3406
|
+
isBlocked: this.isBlocked(),
|
|
3407
|
+
isActive: this.isActive(),
|
|
3408
|
+
resizableEvents: this.resizableEvents(),
|
|
3409
|
+
metadata: this.metadata()
|
|
3410
|
+
};
|
|
3411
|
+
this.store.registerResource(resource);
|
|
3412
|
+
}
|
|
3413
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceEvents, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3414
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: ResourceEvents, isStandalone: true, selector: "mglon-resource", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, avatar: { classPropertyName: "avatar", publicName: "avatar", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null }, isReadOnly: { classPropertyName: "isReadOnly", publicName: "isReadOnly", isSignal: true, isRequired: false, transformFunction: null }, isBlocked: { classPropertyName: "isBlocked", publicName: "isBlocked", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, metadata: { classPropertyName: "metadata", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, resizableEvents: { classPropertyName: "resizableEvents", publicName: "resizableEvents", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
3415
|
+
{
|
|
3416
|
+
provide: RESOURCE_ID_TOKEN,
|
|
3417
|
+
useFactory: (component) => component.id(),
|
|
3418
|
+
deps: [ResourceEvents]
|
|
3419
|
+
}
|
|
3420
|
+
], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
3421
|
+
}
|
|
3422
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResourceEvents, decorators: [{
|
|
3423
|
+
type: Component,
|
|
3424
|
+
args: [{ selector: 'mglon-resource', standalone: true, imports: [CommonModule], template: `<ng-content></ng-content>`, providers: [
|
|
3425
|
+
{
|
|
3426
|
+
provide: RESOURCE_ID_TOKEN,
|
|
3427
|
+
useFactory: (component) => component.id(),
|
|
3428
|
+
deps: [ResourceEvents]
|
|
3429
|
+
}
|
|
3430
|
+
], styles: [":host{display:contents}\n"] }]
|
|
3431
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], avatar: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatar", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }], isReadOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadOnly", required: false }] }], isBlocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isBlocked", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], metadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "metadata", required: false }] }], resizableEvents: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableEvents", required: false }] }] } });
|
|
3432
|
+
|
|
3433
|
+
class Event {
|
|
3434
|
+
id = input.required(...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
3435
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
3436
|
+
startDate = input.required(...(ngDevMode ? [{ debugName: "startDate" }] : []));
|
|
3437
|
+
endDate = input.required(...(ngDevMode ? [{ debugName: "endDate" }] : []));
|
|
3438
|
+
color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : []));
|
|
3439
|
+
allDay = input(DEFAULT_EVENT_INPUTS.allDay, ...(ngDevMode ? [{ debugName: "allDay" }] : []));
|
|
3440
|
+
description = input(DEFAULT_EVENT_INPUTS.description, ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
3441
|
+
data = input(DEFAULT_EVENT_INPUTS.data, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
3442
|
+
/** Optional explicit resource ID (if not nested in mglon-resource) */
|
|
3443
|
+
resourceId = input(...(ngDevMode ? [undefined, { debugName: "resourceId" }] : []));
|
|
3444
|
+
parentResourceId = inject(RESOURCE_ID_TOKEN, { optional: true });
|
|
3445
|
+
/** If true, the event cannot be edited */
|
|
3446
|
+
isReadOnly = input(false, ...(ngDevMode ? [{ debugName: "isReadOnly" }] : []));
|
|
3447
|
+
/** If true, the event is visually blocked (e.g., maintenance) */
|
|
3448
|
+
isBlocked = input(false, ...(ngDevMode ? [{ debugName: "isBlocked" }] : []));
|
|
3449
|
+
/** Flexible user-defined data */
|
|
3450
|
+
metadata = input(...(ngDevMode ? [undefined, { debugName: "metadata" }] : []));
|
|
3451
|
+
/** Tags for filtering and styling */
|
|
3452
|
+
tags = input([], ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
3453
|
+
// --- Interaction Outputs ---
|
|
3454
|
+
eventClick = output();
|
|
3455
|
+
eventDblClick = output();
|
|
3456
|
+
eventContextMenu = output();
|
|
3457
|
+
eventMouseEnter = output();
|
|
3458
|
+
eventMouseLeave = output();
|
|
3459
|
+
eventResizeStart = output();
|
|
3460
|
+
eventResize = output();
|
|
3461
|
+
eventResizeEnd = output();
|
|
3462
|
+
eventDragStart = output();
|
|
3463
|
+
eventDrag = output();
|
|
3464
|
+
eventDragEnd = output();
|
|
3465
|
+
destroy$ = new Subject();
|
|
3466
|
+
store = inject(CalendarStore);
|
|
3467
|
+
ngOnInit() {
|
|
3468
|
+
this.registerEvent();
|
|
3469
|
+
this.setupInteractionListeners();
|
|
3470
|
+
}
|
|
3471
|
+
setupInteractionListeners() {
|
|
3472
|
+
this.store.getInteractions()
|
|
3473
|
+
.pipe(filter(e => e.eventId === this.id()), takeUntil(this.destroy$))
|
|
3474
|
+
.subscribe(e => {
|
|
3475
|
+
switch (e.type) {
|
|
3476
|
+
case 'click':
|
|
3477
|
+
this.eventClick.emit(e.payload);
|
|
3478
|
+
break;
|
|
3479
|
+
case 'dblclick':
|
|
3480
|
+
this.eventDblClick.emit(e.payload);
|
|
3481
|
+
break;
|
|
3482
|
+
case 'contextmenu':
|
|
3483
|
+
this.eventContextMenu.emit(e.payload);
|
|
3484
|
+
break;
|
|
3485
|
+
case 'mouseenter':
|
|
3486
|
+
this.eventMouseEnter.emit(e.payload);
|
|
3487
|
+
break;
|
|
3488
|
+
case 'mouseleave':
|
|
3489
|
+
this.eventMouseLeave.emit(e.payload);
|
|
3490
|
+
break;
|
|
3491
|
+
case 'resizeStart':
|
|
3492
|
+
this.eventResizeStart.emit(e.payload);
|
|
3493
|
+
break;
|
|
3494
|
+
case 'resize':
|
|
3495
|
+
this.eventResize.emit(e.payload);
|
|
3496
|
+
break;
|
|
3497
|
+
case 'resizeEnd':
|
|
3498
|
+
this.eventResizeEnd.emit(e.payload);
|
|
3499
|
+
break;
|
|
3500
|
+
case 'dragStart':
|
|
3501
|
+
this.eventDragStart.emit(e.payload);
|
|
3502
|
+
break;
|
|
3503
|
+
case 'drag':
|
|
3504
|
+
this.eventDrag.emit(e.payload);
|
|
3505
|
+
break;
|
|
3506
|
+
case 'dragEnd':
|
|
3507
|
+
this.eventDragEnd.emit(e.payload);
|
|
3508
|
+
break;
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Lifecycle: Unregister event from store
|
|
3514
|
+
*/
|
|
3515
|
+
ngOnDestroy() {
|
|
3516
|
+
this.destroy$.next();
|
|
3517
|
+
this.destroy$.complete();
|
|
3518
|
+
// Only unregister if the component was fully initialized
|
|
3519
|
+
try {
|
|
3520
|
+
const eventId = this.id();
|
|
3521
|
+
if (eventId) {
|
|
3522
|
+
this.store.unregisterEvent(eventId);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
catch (e) {
|
|
3526
|
+
// Ignore errors during cleanup (e.g., inputs not yet initialized in tests)
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
registerEvent() {
|
|
3530
|
+
const event = {
|
|
3531
|
+
id: this.id(),
|
|
3532
|
+
resourceId: this.resourceId() || this.parentResourceId || undefined,
|
|
3533
|
+
title: this.title(),
|
|
3534
|
+
description: this.description(),
|
|
3535
|
+
tags: this.tags(),
|
|
3536
|
+
start: this.startDate(),
|
|
3537
|
+
end: this.endDate(),
|
|
3538
|
+
color: this.color(),
|
|
3539
|
+
isReadOnly: this.isReadOnly(),
|
|
3540
|
+
isBlocked: this.isBlocked(),
|
|
3541
|
+
isAllDay: this.allDay(),
|
|
3542
|
+
metadata: this.metadata(),
|
|
3543
|
+
type: 'event'
|
|
3544
|
+
};
|
|
3545
|
+
this.store.registerEvent(event);
|
|
3546
|
+
}
|
|
3547
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Event, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3548
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: Event, isStandalone: true, selector: "mglon-event", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, startDate: { classPropertyName: "startDate", publicName: "startDate", isSignal: true, isRequired: true, transformFunction: null }, endDate: { classPropertyName: "endDate", publicName: "endDate", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, allDay: { classPropertyName: "allDay", publicName: "allDay", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, resourceId: { classPropertyName: "resourceId", publicName: "resourceId", isSignal: true, isRequired: false, transformFunction: null }, isReadOnly: { classPropertyName: "isReadOnly", publicName: "isReadOnly", isSignal: true, isRequired: false, transformFunction: null }, isBlocked: { classPropertyName: "isBlocked", publicName: "isBlocked", isSignal: true, isRequired: false, transformFunction: null }, metadata: { classPropertyName: "metadata", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { eventClick: "eventClick", eventDblClick: "eventDblClick", eventContextMenu: "eventContextMenu", eventMouseEnter: "eventMouseEnter", eventMouseLeave: "eventMouseLeave", eventResizeStart: "eventResizeStart", eventResize: "eventResize", eventResizeEnd: "eventResizeEnd", eventDragStart: "eventDragStart", eventDrag: "eventDrag", eventDragEnd: "eventDragEnd" }, ngImport: i0, template: `<!-- Events are managed declaratively -->`, isInline: true, styles: [":host{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
3549
|
+
}
|
|
3550
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Event, decorators: [{
|
|
3551
|
+
type: Component,
|
|
3552
|
+
args: [{ selector: 'mglon-event', standalone: true, imports: [CommonModule], template: `<!-- Events are managed declaratively -->`, styles: [":host{display:none}\n"] }]
|
|
3553
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], startDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "startDate", required: true }] }], endDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "endDate", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], allDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "allDay", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], resourceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "resourceId", required: false }] }], isReadOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadOnly", required: false }] }], isBlocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isBlocked", required: false }] }], metadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "metadata", required: false }] }], tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], eventDblClick: [{ type: i0.Output, args: ["eventDblClick"] }], eventContextMenu: [{ type: i0.Output, args: ["eventContextMenu"] }], eventMouseEnter: [{ type: i0.Output, args: ["eventMouseEnter"] }], eventMouseLeave: [{ type: i0.Output, args: ["eventMouseLeave"] }], eventResizeStart: [{ type: i0.Output, args: ["eventResizeStart"] }], eventResize: [{ type: i0.Output, args: ["eventResize"] }], eventResizeEnd: [{ type: i0.Output, args: ["eventResizeEnd"] }], eventDragStart: [{ type: i0.Output, args: ["eventDragStart"] }], eventDrag: [{ type: i0.Output, args: ["eventDrag"] }], eventDragEnd: [{ type: i0.Output, args: ["eventDragEnd"] }] } });
|
|
3554
|
+
|
|
3555
|
+
const DAY_MAP = {
|
|
3556
|
+
'Sun': RRule.SU,
|
|
3557
|
+
'Mon': RRule.MO,
|
|
3558
|
+
'Tue': RRule.TU,
|
|
3559
|
+
'Wed': RRule.WE,
|
|
3560
|
+
'Thu': RRule.TH,
|
|
3561
|
+
'Fri': RRule.FR,
|
|
3562
|
+
'Sat': RRule.SA
|
|
3563
|
+
};
|
|
3564
|
+
/**
|
|
3565
|
+
* Expands a recurrent event into a list of individual event instances for a given date range.
|
|
3566
|
+
* Each instance has a composite ID: {parentID}_{timestamp}
|
|
3567
|
+
*
|
|
3568
|
+
* @param recurrentEvent The recurrence definition
|
|
3569
|
+
* @param range The search range (DateRange)
|
|
3570
|
+
* @returns Array of Event instances
|
|
3571
|
+
*/
|
|
3572
|
+
function expandRecurrentEvent(recurrentEvent, range) {
|
|
3573
|
+
const { start: rangeStart, end: rangeEnd } = range;
|
|
3574
|
+
const rule = recurrentEvent.recurrenceRule;
|
|
3575
|
+
// Mapping frequency
|
|
3576
|
+
const freqMap = {
|
|
3577
|
+
'daily': RRule.DAILY,
|
|
3578
|
+
'weekly': RRule.WEEKLY,
|
|
3579
|
+
'monthly': RRule.MONTHLY,
|
|
3580
|
+
'yearly': RRule.YEARLY
|
|
3581
|
+
};
|
|
3582
|
+
// Duration of the original event
|
|
3583
|
+
const duration = differenceInMilliseconds(recurrentEvent.end, recurrentEvent.start);
|
|
3584
|
+
// Configure options for RRule
|
|
3585
|
+
const options = {
|
|
3586
|
+
freq: freqMap[rule.type],
|
|
3587
|
+
dtstart: recurrentEvent.start,
|
|
3588
|
+
interval: rule.interval || 1,
|
|
3589
|
+
};
|
|
3590
|
+
if (rule.until)
|
|
3591
|
+
options.until = rule.until;
|
|
3592
|
+
if (rule.count)
|
|
3593
|
+
options.count = rule.count;
|
|
3594
|
+
if (rule.byDay)
|
|
3595
|
+
options.byweekday = rule.byDay.map(day => DAY_MAP[day]);
|
|
3596
|
+
if (rule.byMonth)
|
|
3597
|
+
options.bymonth = rule.byMonth;
|
|
3598
|
+
if (rule.byMonthDay)
|
|
3599
|
+
options.bymonthday = rule.byMonthDay;
|
|
3600
|
+
if (rule.bySetPos)
|
|
3601
|
+
options.bysetpos = rule.bySetPos;
|
|
3602
|
+
if (rule.weekStart)
|
|
3603
|
+
options.wkst = DAY_MAP[rule.weekStart];
|
|
3604
|
+
const rrule = new RRule(options);
|
|
3605
|
+
// Get occurrences within the requested range
|
|
3606
|
+
const occurrences = rrule.between(rangeStart, rangeEnd, true);
|
|
3607
|
+
// Filter out exceptions if they exist (comparing timestamps for precision)
|
|
3608
|
+
const exceptions = recurrentEvent.recurrenceExceptions?.map(d => d.getTime()) || [];
|
|
3609
|
+
return occurrences
|
|
3610
|
+
.filter(date => !exceptions.includes(date.getTime()))
|
|
3611
|
+
.map(date => {
|
|
3612
|
+
// Calculate individual instance end date based on original duration
|
|
3613
|
+
const instanceEnd = addMilliseconds(date, duration);
|
|
3614
|
+
// We extract everything from the recurrent model EXCEPT the recurrence-specific
|
|
3615
|
+
// fields and the 'type' field, to strictly conform to the Event interface.
|
|
3616
|
+
const { recurrenceRule, recurrenceExceptions, type: _parentType, // ignore the 'recurrent' type
|
|
3617
|
+
...baseProps } = recurrentEvent;
|
|
3618
|
+
return {
|
|
3619
|
+
...baseProps,
|
|
3620
|
+
id: `${recurrentEvent.id}_${date.getTime()}`,
|
|
3621
|
+
start: date,
|
|
3622
|
+
end: instanceEnd,
|
|
3623
|
+
type: 'event', // Behave as a normal event for rendering
|
|
3624
|
+
isRecurrenceInstance: true,
|
|
3625
|
+
parentRecurrenceId: recurrentEvent.id,
|
|
3626
|
+
recurrenceDate: date
|
|
3627
|
+
};
|
|
3628
|
+
});
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
class RecurrentEvent {
|
|
3632
|
+
id = input.required(...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
3633
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
3634
|
+
startDate = input.required(...(ngDevMode ? [{ debugName: "startDate" }] : []));
|
|
3635
|
+
endDate = input.required(...(ngDevMode ? [{ debugName: "endDate" }] : []));
|
|
3636
|
+
color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : []));
|
|
3637
|
+
allDay = input(DEFAULT_EVENT_INPUTS.allDay, ...(ngDevMode ? [{ debugName: "allDay" }] : []));
|
|
3638
|
+
description = input(DEFAULT_EVENT_INPUTS.description, ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
3639
|
+
data = input(DEFAULT_EVENT_INPUTS.data, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
3640
|
+
/** Optional explicit resource ID (if not nested in mglon-resource) */
|
|
3641
|
+
resourceId = input(...(ngDevMode ? [undefined, { debugName: "resourceId" }] : []));
|
|
3642
|
+
parentResourceId = inject(RESOURCE_ID_TOKEN, { optional: true });
|
|
3643
|
+
/** If true, the event cannot be edited */
|
|
3644
|
+
isReadOnly = input(false, ...(ngDevMode ? [{ debugName: "isReadOnly" }] : []));
|
|
3645
|
+
/** If true, the event is visually blocked (e.g., maintenance) */
|
|
3646
|
+
isBlocked = input(false, ...(ngDevMode ? [{ debugName: "isBlocked" }] : []));
|
|
3647
|
+
/** Flexible user-defined data */
|
|
3648
|
+
metadata = input(...(ngDevMode ? [undefined, { debugName: "metadata" }] : []));
|
|
3649
|
+
/** Tags for filtering and styling */
|
|
3650
|
+
tags = input([], ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
3651
|
+
/** Recurrence rule defining the pattern */
|
|
3652
|
+
recurrenceRule = input.required(...(ngDevMode ? [{ debugName: "recurrenceRule" }] : [])); // TODO: Type this properly
|
|
3653
|
+
/** Specific dates to exclude */
|
|
3654
|
+
recurrenceExceptions = input(...(ngDevMode ? [undefined, { debugName: "recurrenceExceptions" }] : []));
|
|
3655
|
+
// --- Interaction Outputs ---
|
|
3656
|
+
eventClick = output();
|
|
3657
|
+
eventDblClick = output();
|
|
3658
|
+
eventContextMenu = output();
|
|
3659
|
+
eventMouseEnter = output();
|
|
3660
|
+
eventMouseLeave = output();
|
|
3661
|
+
destroy$ = new Subject();
|
|
3662
|
+
store = inject(CalendarStore);
|
|
3663
|
+
constructor() {
|
|
3664
|
+
effect(() => {
|
|
3665
|
+
// Trigger this effect when range OR any relevant input signal changes
|
|
3666
|
+
this.store.viewRange();
|
|
3667
|
+
this.id();
|
|
3668
|
+
this.title();
|
|
3669
|
+
this.startDate();
|
|
3670
|
+
this.endDate();
|
|
3671
|
+
this.recurrenceRule();
|
|
3672
|
+
untracked(() => {
|
|
3673
|
+
this.registerEvent();
|
|
3674
|
+
});
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
ngOnInit() {
|
|
3678
|
+
this.setupInteractionListeners();
|
|
3679
|
+
}
|
|
3680
|
+
setupInteractionListeners() {
|
|
3681
|
+
this.store.getInteractions()
|
|
3682
|
+
.pipe(filter(e => e.eventId === this.id()), takeUntil(this.destroy$))
|
|
3683
|
+
.subscribe(e => {
|
|
3684
|
+
switch (e.type) {
|
|
3685
|
+
case 'click':
|
|
3686
|
+
this.eventClick.emit(e.payload);
|
|
3687
|
+
break;
|
|
3688
|
+
case 'dblclick':
|
|
3689
|
+
this.eventDblClick.emit(e.payload);
|
|
3690
|
+
break;
|
|
3691
|
+
case 'contextmenu':
|
|
3692
|
+
this.eventContextMenu.emit(e.payload);
|
|
3693
|
+
break;
|
|
3694
|
+
case 'mouseenter':
|
|
3695
|
+
this.eventMouseEnter.emit(e.payload);
|
|
3696
|
+
break;
|
|
3697
|
+
case 'mouseleave':
|
|
3698
|
+
this.eventMouseLeave.emit(e.payload);
|
|
3699
|
+
break;
|
|
3700
|
+
}
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
currentInstanceIds = [];
|
|
3704
|
+
/**
|
|
3705
|
+
* Lifecycle: Unregister event and all its instances from store
|
|
3706
|
+
*/
|
|
3707
|
+
ngOnDestroy() {
|
|
3708
|
+
this.destroy$.next();
|
|
3709
|
+
this.destroy$.complete();
|
|
3710
|
+
this.cleanup();
|
|
3711
|
+
}
|
|
3712
|
+
cleanup() {
|
|
3713
|
+
try {
|
|
3714
|
+
// 1. Unregister all instances
|
|
3715
|
+
this.currentInstanceIds.forEach(id => this.store.unregisterEvent(id));
|
|
3716
|
+
this.currentInstanceIds = [];
|
|
3717
|
+
// 2. Unregister parent
|
|
3718
|
+
const parentId = this.id();
|
|
3719
|
+
if (parentId) {
|
|
3720
|
+
this.store.unregisterEvent(parentId);
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
catch (e) {
|
|
3724
|
+
// Ignore errors during cleanup
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
registerEvent() {
|
|
3728
|
+
// 1. Prepare Parent Master
|
|
3729
|
+
const parentEvent = {
|
|
3730
|
+
id: this.id(),
|
|
3731
|
+
resourceId: this.resourceId() || this.parentResourceId || undefined,
|
|
3732
|
+
title: this.title(),
|
|
3733
|
+
description: this.description(),
|
|
3734
|
+
tags: this.tags(),
|
|
3735
|
+
start: this.startDate(),
|
|
3736
|
+
end: this.endDate(),
|
|
3737
|
+
color: this.color(),
|
|
3738
|
+
isReadOnly: this.isReadOnly(),
|
|
3739
|
+
isBlocked: this.isBlocked(),
|
|
3740
|
+
isAllDay: this.allDay(),
|
|
3741
|
+
metadata: this.metadata(),
|
|
3742
|
+
type: 'recurrent',
|
|
3743
|
+
recurrenceRule: this.recurrenceRule(),
|
|
3744
|
+
recurrenceExceptions: this.recurrenceExceptions()
|
|
3745
|
+
};
|
|
3746
|
+
// 2. Cleanup previous instances before re-registering
|
|
3747
|
+
this.currentInstanceIds.forEach(id => this.store.unregisterEvent(id));
|
|
3748
|
+
this.currentInstanceIds = [];
|
|
3749
|
+
// 3. Register the parent recurrent event (Master definition)
|
|
3750
|
+
this.store.registerEvent(parentEvent);
|
|
3751
|
+
// 4. Expand and register instances for the current range
|
|
3752
|
+
const range = this.store.viewRange();
|
|
3753
|
+
const instances = expandRecurrentEvent(parentEvent, range);
|
|
3754
|
+
instances.forEach(instance => {
|
|
3755
|
+
this.store.registerEvent(instance);
|
|
3756
|
+
this.currentInstanceIds.push(instance.id);
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RecurrentEvent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3760
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: RecurrentEvent, isStandalone: true, selector: "mglon-recurrent-event", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, startDate: { classPropertyName: "startDate", publicName: "startDate", isSignal: true, isRequired: true, transformFunction: null }, endDate: { classPropertyName: "endDate", publicName: "endDate", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, allDay: { classPropertyName: "allDay", publicName: "allDay", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, resourceId: { classPropertyName: "resourceId", publicName: "resourceId", isSignal: true, isRequired: false, transformFunction: null }, isReadOnly: { classPropertyName: "isReadOnly", publicName: "isReadOnly", isSignal: true, isRequired: false, transformFunction: null }, isBlocked: { classPropertyName: "isBlocked", publicName: "isBlocked", isSignal: true, isRequired: false, transformFunction: null }, metadata: { classPropertyName: "metadata", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null }, recurrenceRule: { classPropertyName: "recurrenceRule", publicName: "recurrenceRule", isSignal: true, isRequired: true, transformFunction: null }, recurrenceExceptions: { classPropertyName: "recurrenceExceptions", publicName: "recurrenceExceptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { eventClick: "eventClick", eventDblClick: "eventDblClick", eventContextMenu: "eventContextMenu", eventMouseEnter: "eventMouseEnter", eventMouseLeave: "eventMouseLeave" }, ngImport: i0, template: `<!-- Events are managed declaratively -->`, isInline: true, styles: [":host{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
3761
|
+
}
|
|
3762
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RecurrentEvent, decorators: [{
|
|
3763
|
+
type: Component,
|
|
3764
|
+
args: [{ selector: 'mglon-recurrent-event', standalone: true, imports: [CommonModule], template: `<!-- Events are managed declaratively -->`, styles: [":host{display:none}\n"] }]
|
|
3765
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], startDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "startDate", required: true }] }], endDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "endDate", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], allDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "allDay", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], resourceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "resourceId", required: false }] }], isReadOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadOnly", required: false }] }], isBlocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isBlocked", required: false }] }], metadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "metadata", required: false }] }], tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }], recurrenceRule: [{ type: i0.Input, args: [{ isSignal: true, alias: "recurrenceRule", required: true }] }], recurrenceExceptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "recurrenceExceptions", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], eventDblClick: [{ type: i0.Output, args: ["eventDblClick"] }], eventContextMenu: [{ type: i0.Output, args: ["eventContextMenu"] }], eventMouseEnter: [{ type: i0.Output, args: ["eventMouseEnter"] }], eventMouseLeave: [{ type: i0.Output, args: ["eventMouseLeave"] }] } });
|
|
3766
|
+
|
|
3767
|
+
class FabButtonComponent {
|
|
3768
|
+
size = input('lg', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
3769
|
+
color = input('surface', ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
3770
|
+
// Optional: extended FAB (with text) vs standard (icon only) could be handled via CSS classes check or another input
|
|
3771
|
+
extended = input(false, ...(ngDevMode ? [{ debugName: "extended" }] : []));
|
|
3772
|
+
classes = computed(() => {
|
|
3773
|
+
return [
|
|
3774
|
+
'mglon-fab-button',
|
|
3775
|
+
`size-${this.size()}`,
|
|
3776
|
+
`color-${this.color()}`,
|
|
3777
|
+
this.extended() ? 'extended' : ''
|
|
3778
|
+
].join(' ');
|
|
3779
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
3780
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FabButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3781
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: FabButtonComponent, isStandalone: true, selector: "button[mglon-fab-button], a[mglon-fab-button]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, extended: { classPropertyName: "extended", publicName: "extended", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.data-size": "size()" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;border:none;outline:none;cursor:pointer;-webkit-user-select:none;user-select:none;vertical-align:middle;background-clip:padding-box;text-decoration:none;font-family:var(--mglon-schedule-font-family);border-radius:var(--mglon-fab-radius, 16px);transition:var(--mglon-fab-transition, box-shadow .28s var(--mglon-schedule-transition-easing), background-color .28s var(--mglon-schedule-transition-easing));box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}:host:hover{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}:host.size-md{height:var(--mglon-fab-md-size, 48px);min-width:var(--mglon-fab-md-size, 48px)}:host.size-md.extended{padding:0 20px;border-radius:var(--mglon-fab-extended-radius, 24px)}:host.size-md:not(.extended){width:var(--mglon-fab-md-size, 48px);border-radius:50%}:host.size-lg{height:var(--mglon-fab-lg-size, 56px);min-width:var(--mglon-fab-lg-size, 56px)}:host.size-lg.extended{padding:0 24px;border-radius:var(--mglon-fab-lg-extended-radius, 28px);font-size:14px;font-weight:500;letter-spacing:.25px}:host.size-lg:not(.extended){width:var(--mglon-fab-lg-size, 56px);border-radius:50%;font-size:24px}:host.color-surface{background-color:var(--mglon-fab-surface-bg, var(--mglon-schedule-surface));color:var(--mglon-fab-surface-color, var(--mglon-schedule-text-primary))}:host.color-primary{background-color:var(--mglon-fab-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-fab-primary-color, var(--mglon-schedule-on-primary))}:host.color-primary:hover{background-color:var(--mglon-fab-primary-hover-bg, #185abc)}:host.color-secondary{background-color:var(--mglon-fab-secondary-bg, var(--mglon-schedule-selection));color:var(--mglon-fab-secondary-color, var(--mglon-schedule-primary))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
3782
|
+
}
|
|
3783
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FabButtonComponent, decorators: [{
|
|
3784
|
+
type: Component,
|
|
3785
|
+
args: [{ selector: 'button[mglon-fab-button], a[mglon-fab-button]', standalone: true, imports: [CommonModule], template: '<ng-content></ng-content>', host: {
|
|
3786
|
+
'[class]': 'classes()',
|
|
3787
|
+
'[attr.data-size]': 'size()'
|
|
3788
|
+
}, styles: [":host{display:inline-flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;border:none;outline:none;cursor:pointer;-webkit-user-select:none;user-select:none;vertical-align:middle;background-clip:padding-box;text-decoration:none;font-family:var(--mglon-schedule-font-family);border-radius:var(--mglon-fab-radius, 16px);transition:var(--mglon-fab-transition, box-shadow .28s var(--mglon-schedule-transition-easing), background-color .28s var(--mglon-schedule-transition-easing));box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}:host:hover{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}:host.size-md{height:var(--mglon-fab-md-size, 48px);min-width:var(--mglon-fab-md-size, 48px)}:host.size-md.extended{padding:0 20px;border-radius:var(--mglon-fab-extended-radius, 24px)}:host.size-md:not(.extended){width:var(--mglon-fab-md-size, 48px);border-radius:50%}:host.size-lg{height:var(--mglon-fab-lg-size, 56px);min-width:var(--mglon-fab-lg-size, 56px)}:host.size-lg.extended{padding:0 24px;border-radius:var(--mglon-fab-lg-extended-radius, 28px);font-size:14px;font-weight:500;letter-spacing:.25px}:host.size-lg:not(.extended){width:var(--mglon-fab-lg-size, 56px);border-radius:50%;font-size:24px}:host.color-surface{background-color:var(--mglon-fab-surface-bg, var(--mglon-schedule-surface));color:var(--mglon-fab-surface-color, var(--mglon-schedule-text-primary))}:host.color-primary{background-color:var(--mglon-fab-primary-bg, var(--mglon-schedule-primary));color:var(--mglon-fab-primary-color, var(--mglon-schedule-on-primary))}:host.color-primary:hover{background-color:var(--mglon-fab-primary-hover-bg, #185abc)}:host.color-secondary{background-color:var(--mglon-fab-secondary-bg, var(--mglon-schedule-selection));color:var(--mglon-fab-secondary-color, var(--mglon-schedule-primary))}\n"] }]
|
|
3789
|
+
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], extended: [{ type: i0.Input, args: [{ isSignal: true, alias: "extended", required: false }] }] } });
|
|
3790
|
+
|
|
3791
|
+
/*
|
|
3792
|
+
* Public API Surface of ng-scheduler
|
|
3793
|
+
*/
|
|
3794
|
+
|
|
3795
|
+
/**
|
|
3796
|
+
* Generated bundle index. Do not edit.
|
|
3797
|
+
*/
|
|
3798
|
+
|
|
3799
|
+
export { ButtonComponent, ButtonGroupComponent, DEFAULT_UI_CONFIG, Event, FabButtonComponent, IconButtonComponent, RESOURCE_ID_TOKEN, RecurrentEvent, ResourceEvents, ResourceView, Schedule, WeekView };
|
|
3800
|
+
//# sourceMappingURL=mglon-schedule.mjs.map
|