@evotecit/canopyx 0.3.13 → 0.3.16
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/CanopyX.Html/Assets/canopyx-calendar-edit.js +36 -18
- package/CanopyX.Html/Assets/canopyx-calendar-ics.js +7 -5
- package/CanopyX.Html/Assets/canopyx-calendar.css +124 -116
- package/CanopyX.Html/Assets/canopyx-calendar.js +74 -54
- package/CanopyX.Html/Assets/canopyx-cell-range.css +8 -8
- package/CanopyX.Html/Assets/canopyx-cell-range.js +1 -1
- package/CanopyX.Html/Assets/canopyx-changes.css +113 -113
- package/CanopyX.Html/Assets/canopyx-changes.js +7 -10
- package/CanopyX.Html/Assets/canopyx-column-groups.css +5 -5
- package/CanopyX.Html/Assets/canopyx-column-groups.js +1 -1
- package/CanopyX.Html/Assets/canopyx-data.js +2 -2
- package/CanopyX.Html/Assets/canopyx-detail-rows.css +9 -9
- package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
- package/CanopyX.Html/Assets/canopyx-editing.css +18 -18
- package/CanopyX.Html/Assets/canopyx-editing.js +1 -1
- package/CanopyX.Html/Assets/canopyx-enhance.js +2 -2
- package/CanopyX.Html/Assets/canopyx-facets.css +33 -33
- package/CanopyX.Html/Assets/canopyx-facets.js +5 -6
- package/CanopyX.Html/Assets/canopyx-filter-editor.css +57 -57
- package/CanopyX.Html/Assets/canopyx-filter-editor.js +7 -5
- package/CanopyX.Html/Assets/canopyx-flow-checks.css +13 -11
- package/CanopyX.Html/Assets/canopyx-flow-checks.js +5 -4
- package/CanopyX.Html/Assets/canopyx-flow-command.css +7 -7
- package/CanopyX.Html/Assets/canopyx-flow-command.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-dialog.css +9 -7
- package/CanopyX.Html/Assets/canopyx-flow-dialog.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-drafts.css +7 -7
- package/CanopyX.Html/Assets/canopyx-flow-drafts.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-entry.css +17 -76
- package/CanopyX.Html/Assets/canopyx-flow-entry.js +11 -5
- package/CanopyX.Html/Assets/canopyx-flow-facts.js +2 -0
- package/CanopyX.Html/Assets/canopyx-flow-host.css +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-host.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-module.mjs +11 -5
- package/CanopyX.Html/Assets/canopyx-flow-pick.css +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-pick.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-preview.css +6 -6
- package/CanopyX.Html/Assets/canopyx-flow-preview.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-records.css +3 -3
- package/CanopyX.Html/Assets/canopyx-flow-records.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-tasks.css +17 -17
- package/CanopyX.Html/Assets/canopyx-flow-tasks.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow.css +79 -79
- package/CanopyX.Html/Assets/canopyx-flow.js +8 -7
- package/CanopyX.Html/Assets/canopyx-format-editor.css +23 -23
- package/CanopyX.Html/Assets/canopyx-format-editor.js +2 -3
- package/CanopyX.Html/Assets/canopyx-formatting.css +17 -17
- package/CanopyX.Html/Assets/canopyx-formatting.js +5 -5
- package/CanopyX.Html/Assets/canopyx-forms.css +5 -5
- package/CanopyX.Html/Assets/canopyx-forms.js +1 -1
- package/CanopyX.Html/Assets/canopyx-grid-module.mjs +97 -54
- package/CanopyX.Html/Assets/canopyx-grid-options.css +10 -0
- package/CanopyX.Html/Assets/canopyx-grid-options.js +47 -0
- package/CanopyX.Html/Assets/canopyx-grid.css +199 -261
- package/CanopyX.Html/Assets/canopyx-grid.js +97 -54
- package/CanopyX.Html/Assets/canopyx-grouping.css +17 -17
- package/CanopyX.Html/Assets/canopyx-grouping.js +12 -9
- package/CanopyX.Html/Assets/canopyx-highlight.css +14 -14
- package/CanopyX.Html/Assets/canopyx-highlight.js +2 -2
- package/CanopyX.Html/Assets/canopyx-interactive.css +94 -155
- package/CanopyX.Html/Assets/canopyx-interactive.js +33 -14
- package/CanopyX.Html/Assets/canopyx-labels-de.js +5 -0
- package/CanopyX.Html/Assets/canopyx-labels-es.js +5 -0
- package/CanopyX.Html/Assets/canopyx-labels-fr.js +5 -0
- package/CanopyX.Html/Assets/canopyx-labels-it.js +5 -0
- package/CanopyX.Html/Assets/canopyx-labels-pl.js +5 -0
- package/CanopyX.Html/Assets/canopyx-layouts.css +17 -17
- package/CanopyX.Html/Assets/canopyx-layouts.js +2 -10
- package/CanopyX.Html/Assets/canopyx-link.css +7 -7
- package/CanopyX.Html/Assets/canopyx-link.js +1 -1
- package/CanopyX.Html/Assets/canopyx-live.css +9 -9
- package/CanopyX.Html/Assets/canopyx-live.js +20 -15
- package/CanopyX.Html/Assets/canopyx-matrix.css +74 -74
- package/CanopyX.Html/Assets/canopyx-matrix.js +3 -4
- package/CanopyX.Html/Assets/canopyx-module.mjs +33 -14
- package/CanopyX.Html/Assets/canopyx-pager.css +2 -2
- package/CanopyX.Html/Assets/canopyx-pager.js +1 -1
- package/CanopyX.Html/Assets/canopyx-presenters.js +2 -2
- package/CanopyX.Html/Assets/canopyx-record-panel.css +57 -49
- package/CanopyX.Html/Assets/canopyx-record-panel.js +13 -8
- package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +101 -57
- package/CanopyX.Html/Assets/canopyx-reporting.css +214 -276
- package/CanopyX.Html/Assets/canopyx-reporting.js +101 -57
- package/CanopyX.Html/Assets/canopyx-timeline.css +28 -28
- package/CanopyX.Html/Assets/canopyx-timeline.js +11 -10
- package/CanopyX.Html/Assets/canopyx-tree-tools.css +47 -47
- package/CanopyX.Html/Assets/canopyx-tree-tools.js +3 -3
- package/CanopyX.Html/Assets/canopyx.css +81 -142
- package/CanopyX.Html/Assets/canopyx.js +33 -14
- package/JavaScript/calendar.d.ts +15 -0
- package/JavaScript/flow.d.ts +6 -0
- package/JavaScript/grid.d.ts +29 -4
- package/JavaScript/live.d.ts +2 -0
- package/JavaScript/locales/de.mjs +5 -0
- package/JavaScript/locales/es.mjs +5 -0
- package/JavaScript/locales/fr.mjs +5 -0
- package/JavaScript/locales/it.mjs +5 -0
- package/JavaScript/locales/pl.mjs +5 -0
- package/JavaScript/plugins.d.ts +4 -0
- package/JavaScript/protocol.d.ts +1 -1
- package/JavaScript/records.d.ts +18 -4
- package/README.md +6 -2
- package/package.json +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(global){"use strict";const installed=new WeakSet();const part={version:"
|
|
1
|
+
(function(global){"use strict";const installed=new WeakSet();const part={version:"463aadb3a2169631",install(context){if(installed.has(context.DataGrid.prototype))return;const{toneVariable,DataGrid,h,setText,fill,plural,gridButton,gridIcon,gridFreeze,cellText,cellValue,isCellObject,gridQuery,gridQueryRows,gridResultShape,gridPresenterTone,validateGridRows,validateGridPage,validateGridCursorPage,validStructuralCount,checkDocument,numberFormat,HostCellContext,disposeHostSlot,loadPart,PROTOCOL}=context;const CALENDAR_LABELS={calendarToday:"Today",calendarPrevious:"Previous period",calendarNext:"Next period",calendarViews:"Calendar view",calendarMonth:"Month",calendarWeek:"Week",calendarDay:"Day",calendarDays:"{count} days",calendarAgenda:"Agenda",calendarYear:"Year",calendarTimeline:"Timeline",calendarGoTo:"Go to date",calendarAllDay:"All day",calendarMore:"+{count} more",calendarMoreLabel:"Show {count} more events on {date}",calendarDayEvents:"Events on {date}",calendarNoEvents:"No events in this period",calendarEvents:"[one]{count} event[other]{count} events",calendarShowing:"{view}, {range}",calendarRepeats:"Repeats",calendarTruncated:"Showing the first {count} events. Narrow the period or the filters to see the rest.",calendarLoading:"Loading events…",calendarLoadError:"Could not load events: {error}",calendarNow:"Now",calendarZoom:"Zoom",calendarZoomHour:"Hours",calendarZoomDay:"Days",calendarZoomWeek:"Weeks",calendarZoomMonth:"Months",calendarZoomQuarter:"Quarters",calendarResources:"Resources",calendarCapacity:"Concurrent events",calendarCapacityAt:"{count} at once",calendarBusy:"{hours} h busy",calendarOverlaps:"[one]{count} overlap[other]{count} overlaps",calendarUnavailable:"Unavailable",calendarOutsideHours:"Outside working hours",calendarConflict:"Overlaps {title}",calendarNotAllowed:"{resource} cannot take this event",calendarMoved:"{title} moved to {time}",calendarResized:"{title} now ends {time}",calendarSeriesMoved:"Every occurrence of {title} moved",calendarSeriesTime:"Only the time of a whole series can be moved here.",calendarMoveHelp:"Arrow keys move, Shift with arrow keys changes the end, Alt with Up or Down changes the resource, Enter saves, Escape cancels.",calendarEdit:"Move or resize {title}",calendarSaveFailed:"{title} was not saved: {error}",calendarExport:"iCalendar (.ics)",calendarTimeRange:"{start} – {end}",calendarSelected:"Selected {range}",calendarLane:"{resource}, {events}",calendarHighlighted:"[one]{count} event highlighted[other]{count} events highlighted",calendarMini:"Mini month",calendarWeekList:"Week list",calendarDayList:"Day list",calendarMonths:"Months",calendarWeekNumber:"Week {number}",calendarWeekShort:"W{number}",calendarNewEvent:"New event",calendarEventTitle:"Title",calendarCreate:"Add",calendarCancel:"Cancel",calendarCreated:"{title} added on {time}",calendarReceived:"{title} scheduled on {time}",calendarOccurrenceTitle:"Change a repeating event",calendarOccurrenceReadOnly:"This part of the series cannot be saved because some values are read-only.",calendarEditOccurrence:"Edit this occurrence",calendarRemoveOccurrence:"Remove this occurrence",calendarOccurrenceScope:"Only {date} changes; other dates of {title} stay as they are.",calendarSharedResources:"{count} {resources}",calendarResourceWindow:"{resources} {start}–{end} of {total}",calendarThisEvent:"This event",calendarThisAndFollowing:"This and following events",calendarAllEvents:"All events",calendarSources:"Calendars",calendarImportFailed:"Could not read {source}: {error}",calendarActions:"Actions for {title}",calendarOpen:"Open",calendarMove:"Move",calendarNewHere:"New event here",calendarOutsideRange:"Outside the dates this calendar shows",calendarClose:"Close",calendarDetailsOf:"Details of {title}"};function calLabelsFor(labels){const out={...CALENDAR_LABELS};for(const name of Object.keys(CALENDAR_LABELS))if(Object.hasOwn(labels,name))out[name]=labels[name];return Object.freeze(out)}
|
|
2
2
|
function calCount(grid,template,count,values){return fill(plural(template,count,grid.lang),{...values,count:numberFormat(grid.lang).format(count)})}
|
|
3
3
|
const CAL_DAY=86400000,CAL_MINUTE=60000,CAL_HOUR=3600000;const calZones=new Map();function calDayNumber(y,m,d){return Math.floor(Date.UTC(y,m-1,d)/ CAL_DAY); }
|
|
4
4
|
function calDayParts(day){const date=new Date(day*CAL_DAY);return{y:date.getUTCFullYear(),m:date.getUTCMonth()+ 1,d:date.getUTCDate(),dow:date.getUTCDay()}}
|
|
@@ -31,16 +31,16 @@ function calFirstDay(lang){try{const locale=new Intl.Locale(lang||"en-US"),info=
|
|
|
31
31
|
function calFormats(lang,zone){const cache=new Map(),timeZone=zone.name==="utc"?"UTC":zone.name==="local"?undefined:zone.name;const make=(options,dayFormat)=>{const key=JSON.stringify(options)+(dayFormat?"d":"t");let format=cache.get(key);if(!format){const settings={...options,timeZone:dayFormat?"UTC":timeZone};try{format=new Intl.DateTimeFormat(lang||undefined,settings)}catch(_){format=new Intl.DateTimeFormat(undefined,settings)}
|
|
32
32
|
cache.set(key,format)}
|
|
33
33
|
return format};const day=(options,value)=>make(options,true).format(new Date(value*CAL_DAY));const range=(options,from,to)=>{if(from===to)return day(options,from);const format=make(options,true);try{return format.formatRange(new Date(from*CAL_DAY),new Date(to*CAL_DAY))}catch(_){return format.format(new Date(from*CAL_DAY))+ " – "+format.format(new Date(to*CAL_DAY))}};return{lang,day,range,monthTitle:value=>day({month:"long",year:"numeric"},value),monthName:value=>day({month:"long"},value),monthShort:value=>day({month:"short"},value),weekday:(value,width)=>day({weekday:width||"short"},value),dayNumber:value=>day({day:"numeric"},value),dayHeader:value=>day({weekday:"short",day:"numeric"},value),longDay:value=>day({weekday:"long",day:"numeric",month:"long",year:"numeric"},value),shortDay:value=>day({day:"numeric",month:"short"},value),date:value=>day({day:"numeric",month:"short",year:"numeric"},value),year:value=>day({year:"numeric"},value),dayRange:(from,to)=>range({day:"numeric",month:"short",year:"numeric"},from,to),monthRange:(from,to)=>range({month:"short",year:"numeric"},from,to),time:value=>make({hour:"numeric",minute:"2-digit"}).format(new Date(value)),hour:minutes=>make({hour:"numeric"},true).format(new Date(minutes*CAL_MINUTE)),clock:minutes=>make({hour:"numeric",minute:"2-digit"},true).format(new Date(minutes*CAL_MINUTE)),number:value=>numberFormat(lang).format(value)}}
|
|
34
|
-
const CAL_VIEWS=["month","week","day","days","agenda","year","timeline","mini","weekList","dayList","months"],CAL_ZOOMS=["hour","day","week","month","quarter"];const CAL_KEYS=new Set(["start","end","title","subtitle","allDay","resource","status","recurrence","exceptions","series","recurrenceId","display","seriesEnd","view","views","date","days","firstDay","hiddenDays","slotMinutes","minTime","maxTime","scrollTime","workingHours","nowIndicator","eventLimit","eventStyle","timeZone","editable","resources","zoom","zooms","capacity","ranges","availability","limit","phone","toolbar","details","description","tone","source","sources","create","drop","occurrence","weekNumbers","navLinks","validRange","fixedWeeks","outsideDays","span","menu","hover","yearEvents","agendaSpan","months","print"]);const CAL_TONE=/^[a-z][a-z0-9-]{0,31}$/;
|
|
34
|
+
const CAL_VIEWS=["month","week","day","days","agenda","year","timeline","mini","weekList","dayList","months"],CAL_ZOOMS=["hour","day","week","month","quarter"];const CAL_KEYS=new Set(["start","end","title","subtitle","allDay","resource","resourceIds","status","recurrence","exceptions","series","recurrenceId","display","seriesEnd","view","views","date","days","firstDay","hiddenDays","slotMinutes","minTime","maxTime","scrollTime","workingHours","nowIndicator","eventLimit","eventStyle","timeZone","editable","resources","zoom","zooms","capacity","ranges","availability","limit","phone","toolbar","details","description","tone","source","sources","create","drop","occurrence","weekNumbers","navLinks","validRange","fixedWeeks","outsideDays","span","menu","hover","yearEvents","agendaSpan","months","print"]);const CAL_TONE=/^[a-z][a-z0-9-]{0,31}$/;
|
|
35
35
|
function calInvalid(message){throw new Error("CanopyX: "+message)}
|
|
36
36
|
function calDays(list,name){if(!Array.isArray(list)||list.length>7||new Set(list).size!==list.length||list.some(day=>!Number.isInteger(day)||day<0||day>6))calInvalid(name+" lists weekdays 0 to 6.");return new Set(list)}
|
|
37
37
|
function calTimeRange(value,name){const start=calMinutes(value?.start),end=calMinutes(value?.end);if(!(start<end))calInvalid(name+" needs a start before its end (HH:mm).");return{start,end}}
|
|
38
|
-
function calSettings(grid){const input=grid.view.calendar,columns=new Map(grid.columns.map(column=>[column.id,column]));if(!input||typeof input!=="object"||Array.isArray(input))calInvalid("calendar settings are an object.");for(const key of Object.keys(input))if(!CAL_KEYS.has(key))calInvalid("calendar settings have no '"+key+"'.");const column=(name,kinds,required)=>{const id=input[name];if(id===undefined&&!required)return null;const found=columns.get(id);if(!found||kinds&&!kinds.includes(found.kind))calInvalid("calendar "+name+" names a " +(kinds?kinds.join(" or ")+ " ":"")+ "column.");return found};const settings={start:column("start",["datetime"],true),end:column("end",["datetime"]),allDay:column("allDay",["text"]),title:column("title")||grid.columns.find(item=>item.rowHeader)||grid.columns.find(item=>!item.hidden),subtitle:column("subtitle"),resource:column("resource",["text"]),status:column("status"),recurrence:column("recurrence",["text"]),exceptions:column("exceptions",["text"]),series:column("series",["text"]),recurrenceId:column("recurrenceId",["datetime"]),display:column("display",["text"]),seriesEnd:column("seriesEnd",["datetime"])};if(!settings.series!==!settings.recurrenceId)calInvalid("calendar series and recurrenceId come together.");const views=input.views===undefined?["month","week","day","agenda"].concat(input.resources?["timeline"]:[]):input.views;if(!Array.isArray(views)||!views.length||new Set(views).size!==views.length||views.some(view=>!CAL_VIEWS.includes(view)))calInvalid("calendar views list unique views.");const view=input.view===undefined?views[0]:input.view;if(!views.includes(view))calInvalid("the calendar's first view must be one of its views.");settings.views=views;settings.view=view;settings.date=input.date===undefined?null:calParseDay(input.date);if(Number.isNaN(settings.date))calInvalid("calendar date is yyyy-MM-dd.");const integer=(name,min,max,fallback)=>{const value=input[name]===undefined?fallback:input[name];if(!Number.isInteger(value)||value<min||value>max)calInvalid("calendar "+name+" is an integer from "+min+" to "+max+".");return value};settings.days=integer("days",2,14,3);settings.firstDay=input.firstDay===undefined?calFirstDay(grid.lang):integer("firstDay",0,6,0);settings.hidden=input.hiddenDays===undefined?new Set():calDays(input.hiddenDays,"calendar hiddenDays");if(settings.hidden.size===7)calInvalid("calendar hiddenDays must leave a day.");settings.slotMinutes=input.slotMinutes===undefined?30:input.slotMinutes;if(![5,10,15,20,30,60].includes(settings.slotMinutes))calInvalid("calendar slotMinutes is 5, 10, 15, 20, 30 or 60.");const time=(name,fallback)=>{const value=input[name]===undefined?fallback:calMinutes(input[name]);if(Number.isNaN(value))calInvalid("calendar "+name+" is HH:mm.");return value};settings.minMinutes=time("minTime",0);settings.maxMinutes=time("maxTime",1440);settings.scrollMinutes=time("scrollTime",480);if(settings.minMinutes>=settings.maxMinutes||settings.minMinutes%settings.slotMinutes||settings.maxMinutes%settings.slotMinutes)
|
|
38
|
+
function calSettings(grid){const input=grid.view.calendar,columns=new Map(grid.columns.map(column=>[column.id,column]));if(!input||typeof input!=="object"||Array.isArray(input))calInvalid("calendar settings are an object.");for(const key of Object.keys(input))if(!CAL_KEYS.has(key))calInvalid("calendar settings have no '"+key+"'.");const column=(name,kinds,required)=>{const id=input[name];if(id===undefined&&!required)return null;const found=columns.get(id);if(!found||kinds&&!kinds.includes(found.kind))calInvalid("calendar "+name+" names a " +(kinds?kinds.join(" or ")+ " ":"")+ "column.");return found};const settings={start:column("start",["datetime"],true),end:column("end",["datetime"]),allDay:column("allDay",["text"]),title:column("title")||grid.columns.find(item=>item.rowHeader)||grid.columns.find(item=>!item.hidden),subtitle:column("subtitle"),resource:column("resource",["text"]),resourceIds:column("resourceIds",["text"]),status:column("status"),recurrence:column("recurrence",["text"]),exceptions:column("exceptions",["text"]),series:column("series",["text"]),recurrenceId:column("recurrenceId",["datetime"]),display:column("display",["text"]),seriesEnd:column("seriesEnd",["datetime"])};if(!settings.series!==!settings.recurrenceId)calInvalid("calendar series and recurrenceId come together.");const views=input.views===undefined?["month","week","day","agenda"].concat(input.resources?["timeline"]:[]):input.views;if(!Array.isArray(views)||!views.length||new Set(views).size!==views.length||views.some(view=>!CAL_VIEWS.includes(view)))calInvalid("calendar views list unique views.");const view=input.view===undefined?views[0]:input.view;if(!views.includes(view))calInvalid("the calendar's first view must be one of its views.");settings.views=views;settings.view=view;settings.date=input.date===undefined?null:calParseDay(input.date);if(Number.isNaN(settings.date))calInvalid("calendar date is yyyy-MM-dd.");const integer=(name,min,max,fallback)=>{const value=input[name]===undefined?fallback:input[name];if(!Number.isInteger(value)||value<min||value>max)calInvalid("calendar "+name+" is an integer from "+min+" to "+max+".");return value};settings.days=integer("days",2,14,3);settings.firstDay=input.firstDay===undefined?calFirstDay(grid.lang):integer("firstDay",0,6,0);settings.hidden=input.hiddenDays===undefined?new Set():calDays(input.hiddenDays,"calendar hiddenDays");if(settings.hidden.size===7)calInvalid("calendar hiddenDays must leave a day.");settings.slotMinutes=input.slotMinutes===undefined?30:input.slotMinutes;if(![5,10,15,20,30,60].includes(settings.slotMinutes))calInvalid("calendar slotMinutes is 5, 10, 15, 20, 30 or 60.");const time=(name,fallback)=>{const value=input[name]===undefined?fallback:calMinutes(input[name]);if(Number.isNaN(value))calInvalid("calendar "+name+" is HH:mm.");return value};settings.minMinutes=time("minTime",0);settings.maxMinutes=time("maxTime",1440);settings.scrollMinutes=time("scrollTime",480);if(settings.minMinutes>=settings.maxMinutes||settings.minMinutes%settings.slotMinutes||settings.maxMinutes%settings.slotMinutes)
|
|
39
39
|
calInvalid("calendar minTime comes before maxTime, both on slot boundaries.");if(input.workingHours!==undefined){const hours=input.workingHours;if(!hours||typeof hours!=="object"||Object.keys(hours).some(key=>!["days","start","end"].includes(key)))calInvalid("calendar workingHours takes days, start and end.");settings.working={...calTimeRange(hours,"calendar workingHours"),days:calDays(hours.days===undefined?[1,2,3,4,5]:hours.days,"calendar workingHours days")}}else settings.working=null;for(const name of["nowIndicator"])if(input[name]!==undefined&&typeof input[name]!=="boolean")calInvalid("calendar "+name+" is a boolean.");settings.now=input.nowIndicator!==false;settings.eventLimit=input.eventLimit===undefined||input.eventLimit==="auto"?"auto":integer("eventLimit",1,20,3);settings.eventStyle=input.eventStyle===undefined?"quiet":input.eventStyle;if(!["quiet","strong"].includes(settings.eventStyle))calInvalid("calendar eventStyle is quiet or strong.");settings.zoneName=input.timeZone===undefined?grid.local?"local":"utc":input.timeZone;if(typeof settings.zoneName!=="string"||!calZone(settings.zoneName))calInvalid("calendar timeZone is utc, local or a time zone the browser knows.");settings.zoneFixed=input.timeZone!==undefined;settings.limit=integer("limit",1,100000,20000);settings.phone=input.phone===undefined?null:input.phone;if(settings.phone!==null&&!views.includes(settings.phone))calInvalid("calendar phone names one of the calendar's views.");settings.editable=calEditable(input.editable,columns,settings);settings.zooms=input.zooms===undefined?CAL_ZOOMS.slice():input.zooms;if(!Array.isArray(settings.zooms)||!settings.zooms.length||new Set(settings.zooms).size!==settings.zooms.length||settings.zooms.some(zoom=>!CAL_ZOOMS.includes(zoom)))
|
|
40
40
|
calInvalid("calendar zooms list unique zoom levels.");settings.zoom=input.zoom===undefined?settings.zooms.includes("day")?"day":settings.zooms[0]:input.zoom;if(!settings.zooms.includes(settings.zoom))calInvalid("calendar zoom must be one of its zooms.");settings.resources=calResources(input.resources);settings.capacity=calCapacity(input.capacity);settings.ranges=calRanges(input.ranges);settings.availability=calAvailability(input.availability);calSettingsMore(input,column,settings);return settings}
|
|
41
41
|
function calEditable(value,columns,settings){if(value===undefined||value===false)return null;const input=value===true?{}:value;if(!input||typeof input!=="object"||Array.isArray(input)||
|
|
42
42
|
Object.keys(input).some(key=>!["move","resize","resources","overlap","workingHours","allowedResources","snapMinutes","commit"].includes(key)))
|
|
43
|
-
calInvalid("calendar editable takes move, resize, resources, overlap, workingHours, allowedResources, snapMinutes and commit.");for(const name of["move","resize","resources","overlap","workingHours"])if(input[name]!==undefined&&typeof input[name]!=="boolean")calInvalid("calendar editable "+name+" is a boolean.");const allowed=input.allowedResources===undefined?null:columns.get(input.allowedResources);if(input.allowedResources!==undefined&&(!allowed||allowed.kind!=="text"))calInvalid("calendar editable allowedResources names a text column.");const snap=input.snapMinutes===undefined?settings.slotMinutes:input.snapMinutes;if(![1,5,10,15,20,30,60].includes(snap))calInvalid("calendar editable snapMinutes is 1, 5, 10, 15, 20, 30 or 60.");if(input.commit!==undefined&&!["auto","manual"].includes(input.commit))calInvalid("calendar editable commit is auto or manual.");return{move:input.move!==false,resize:input.resize!==false&&!!settings.end,resources:input.resources!==false&&!!settings.resource,overlap:input.overlap!==false,working:!!input.workingHours,allowed,snap,commit:input.commit||"auto"}}
|
|
43
|
+
calInvalid("calendar editable takes move, resize, resources, overlap, workingHours, allowedResources, snapMinutes and commit.");for(const name of["move","resize","resources","overlap","workingHours"])if(input[name]!==undefined&&typeof input[name]!=="boolean")calInvalid("calendar editable "+name+" is a boolean.");const allowed=input.allowedResources===undefined?null:columns.get(input.allowedResources);if(input.allowedResources!==undefined&&(!allowed||allowed.kind!=="text"))calInvalid("calendar editable allowedResources names a text column.");const snap=input.snapMinutes===undefined?settings.slotMinutes:input.snapMinutes;if(![1,5,10,15,20,30,60].includes(snap))calInvalid("calendar editable snapMinutes is 1, 5, 10, 15, 20, 30 or 60.");if(input.commit!==undefined&&!["auto","manual"].includes(input.commit))calInvalid("calendar editable commit is auto or manual.");return{move:input.move!==false,resize:input.resize!==false&&!!settings.end,resources:input.resources!==false&&!!(settings.resource||settings.resourceIds),overlap:input.overlap!==false,working:!!input.workingHours,allowed,snap,commit:input.commit||"auto"}}
|
|
44
44
|
function calResources(value){if(value===undefined)return null;if(!value||typeof value!=="object"||Array.isArray(value)||Object.keys(value).some(key=>!["view","width","label","aggregates"].includes(key))||
|
|
45
45
|
!value.view||value.view.type!=="view")calInvalid("calendar resources hold a tree view and optional width, label and aggregates.");if(value.width!==undefined&&(!Number.isInteger(value.width)||value.width<120||value.width>800))calInvalid("calendar resources width is 120 to 800 pixels.");if(value.label!==undefined&&(typeof value.label!=="string"||!value.label.trim()||value.label.length>256))calInvalid("calendar resources label is text.");const aggregates=value.aggregates||{};if(typeof aggregates!=="object"||Array.isArray(aggregates)||Object.entries(aggregates).some(([id,kind])=>
|
|
46
46
|
!(value.view.columns||[]).some(column=>column.id===id)||!["count","busy","overlap"].includes(kind)))calInvalid("calendar resource aggregates map tree columns to count, busy or overlap.");return{view:value.view,width:value.width||280,label:value.label||null,aggregates}}
|
|
@@ -57,11 +57,15 @@ calInvalid("calendar availability entries take resource, days, start and end.");
|
|
|
57
57
|
const CAL_TOOLBAR=["search","filters","columns","views","export","more"],CAL_ID=/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
|
|
58
58
|
function calSettingsMore(input,column,settings){const flag=(name,fallback)=>{if(input[name]===undefined)return fallback;if(typeof input[name]!=="boolean")calInvalid("calendar "+name+" is a boolean.");return input[name]};const choice=(name,values,fallback)=>{if(input[name]===undefined)return fallback;if(!values.includes(input[name]))calInvalid("calendar "+name+" is "+values.join(", ")+ ".");return input[name]};const toolbar=input.toolbar;if(toolbar!==undefined&&typeof toolbar!=="boolean"&&!(Array.isArray(toolbar)&&new Set(toolbar).size===toolbar.length&&toolbar.every(item=>CAL_TOOLBAR.includes(item))))
|
|
59
59
|
calInvalid("calendar toolbar is true, false or a list of "+CAL_TOOLBAR.join(", ")+ ".");settings.toolbar=toolbar===undefined||toolbar===true?true:toolbar===false?false:new Set(toolbar);settings.details=choice("details",["drawer","popover"],"drawer");settings.description=column("description",["text"]);settings.tone=column("tone",["text"]);settings.source=column("source",["text"]);settings.sources=calSources(input.sources,settings.source);settings.create=calCreate(input.create,settings);settings.drop=flag("drop",false);if((settings.create||settings.drop)&&(!settings.start.editable||!settings.title?.editable))calInvalid("calendar create and drop need editable start and title columns.");const overrides=!!(settings.series?.editable&&settings.recurrenceId?.editable);settings.occurrence=choice("occurrence",["ask","this","following","all"],overrides?"ask":"all");if(!overrides&&settings.occurrence!=="all")calInvalid("calendar occurrence "+settings.occurrence+" needs editable series and recurrenceId columns.");settings.weekNumbers=input.weekNumbers===undefined||input.weekNumbers===false?false:choice("weekNumbers",["iso","locale"],false);settings.navLinks=flag("navLinks",true);settings.validRange=calValidRange(input.validRange);settings.fixedWeeks=flag("fixedWeeks",false);settings.outsideDays=flag("outsideDays",true);settings.span=calSpan(input.span);settings.menu=flag("menu",true);settings.hover=flag("hover",true);settings.yearEvents=flag("yearEvents",false);settings.print=flag("print",false);settings.agendaSpan=choice("agendaSpan",["day","week","month","year"],"month");if(input.months!==undefined&&(!Number.isInteger(input.months)||input.months<2||input.months>24))calInvalid("calendar months is an integer from 2 to 24.");settings.months=input.months??6}
|
|
60
|
-
function calSources(list,sourceColumn){if(list===undefined)return[];if(!Array.isArray(list)||!list.length||list.length>50)calInvalid("calendar sources are a list of 1 to 50.");const ids=new Set();return list.map(item=>{if(!item||typeof item!=="object"||Array.isArray(item)||Object.keys(item).some(key=>!["id","label","tone","ics","hidden"].includes(key))||
|
|
60
|
+
function calSources(list,sourceColumn){if(list===undefined)return[];if(!Array.isArray(list)||!list.length||list.length>50)calInvalid("calendar sources are a list of 1 to 50.");const ids=new Set();return list.map(item=>{if(!item||typeof item!=="object"||Array.isArray(item)||Object.keys(item).some(key=>!["id","label","tone","ics","url","fetchParams","color","hidden"].includes(key))||
|
|
61
61
|
typeof item.id!=="string"||!CAL_ID.test(item.id)||ids.has(item.id)||typeof item.label!=="string"||!item.label.trim()||item.label.length>256||
|
|
62
|
-
item.tone!==undefined&&!CAL_TONE.test(item.tone)||item.
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
item.tone!==undefined&&!CAL_TONE.test(item.tone)||item.url!==undefined&&(!calSameOrigin(item.url)||item.ics!==undefined)||
|
|
63
|
+
item.color!==undefined&&!/^#[0-9a-fA-F]{6}$/.test(item.color) || !calFetchParams(item.fetchParams, item.url) || item.ics !== undefined && !calSameOrigin(item.ics) || item.hidden !== undefined && typeof item.hidden !== "boolean")
|
|
64
|
+
calInvalid("a calendar source has a unique id, a label, and optional tone, ics (a same-origin address) and hidden.");if(item.ics===undefined&&item.url===undefined&&!sourceColumn)calInvalid("calendar sources without ics need the source column.");ids.add(item.id);return{id:item.id,label:item.label,tone:calSafeTone(item.tone),ics:item.ics??null,url:item.url??null,fetchParams:item.fetchParams||{},color:item.color??null,hidden:item.hidden===true}})}
|
|
65
|
+
function calFetchParams(value,url){if(value===undefined)return true;return!!url&&value&&typeof value==="object"&&!Array.isArray(value)&&Object.keys(value).length<=50&&
|
|
66
|
+
Object.entries(value).every(([key,text])=>key.trim()&&key.length<=128&&!["start","end","timeZone"].includes(key)&&typeof text==="string"&&text.length<=2048)}
|
|
67
|
+
function calSameOrigin(value){if(typeof value!=="string"||!value||value.length>2048||/[\s\\\x00-\x1f\x7f]/.test(value)) return false;
|
|
68
|
+
try{const base=typeof location!=="undefined"?location.href:"https://localhost/",url=new URL(value,base);if(!/^[a-z][a-z0-9+.-]*:|^\/\//i.test(value)) return true;
|
|
65
69
|
return(url.protocol==="http:"||url.protocol==="https:")&&url.origin!=="null"&&url.origin===new URL(base).origin}catch{return false}}
|
|
66
70
|
function calCreate(value,settings){if(value===undefined||value===false)return null;const input=value===true?{}:value;if(!input||typeof input!=="object"||Array.isArray(input)||Object.keys(input).some(key=>!["minutes","allDay"].includes(key))||
|
|
67
71
|
input.minutes!==undefined&&(!Number.isInteger(input.minutes)||input.minutes<5||input.minutes>1440)||input.allDay!==undefined&&typeof input.allDay!=="boolean")
|
|
@@ -77,22 +81,24 @@ return firstDay>=valid.first&&lastDay<=valid.last},calDisplaySpan(event){return
|
|
|
77
81
|
}});const CAL_BASES=new WeakMap(),CAL_UTC=()=>calZone("utc"),CAL_TONE_NAME=/^[a-z][a-z0-9-]{0,31}$/;
|
|
78
82
|
const calRec=()=>DataGrid.prototype.calKit.calRule?DataGrid.prototype.calKit:null;function calSafeTone(tone){return typeof tone==="string"&&CAL_TONE_NAME.test(tone)&&!tone.endsWith("-bg")?tone:null}
|
|
79
83
|
function calDateOf(value){return typeof value==="string"?calParseDay(value.slice(0,10)):NaN}
|
|
84
|
+
function calNativeIdentity(id){return /^(json:|grid:)/.test(id) ? "grid:" + id : id; }
|
|
80
85
|
function calBase(view,row){const cached=CAL_BASES.get(row);if(cached&&cached.view===view&&cached.version===view.calVersion)return cached.base;const cal=view.cal,cells=row.cells,fields=row.external?row.fields:null;const read=column=>column&&!fields?cellValue(cells[column.id]):null,value=name=>fields?fields[name]??null:read(cal[name]);let base=null;const start=calInstant(value("start"));if(Number.isFinite(start)){const allDay=value("allDay")===true||value("allDay")==="true";let end=calInstant(value("end"));if(allDay){const first=calDateOf(value("start")),endText=value("end"),last=calDateOf(endText);const endDay=Number.isFinite(end)&&Number.isFinite(last)?(/T00:00(?::00(?:\.0+)?)?(?:Z|[+-]|$)/.test(endText) ? last : last + 1) : NaN;
|
|
81
|
-
base={start:first*CAL_DAY,end:(endDay>first?endDay:first+1)*CAL_DAY}}else base={start,end:Number.isFinite(end)&&end>=start?end:start};const ownTone=cal.tone&&!fields?calSafeTone(String(read(cal.tone)??"").trim()):null;Object.assign(base,{key:row.id,row,allDay,marker:!allDay&&base.end===base.start,title:(fields?fields.title:cellText(cells[cal.title.id],cal.title,view.fmt))||row.id,subtitle:cal.subtitle&&!fields?cellText(cells[cal.subtitle.id],cal.subtitle,view.fmt):"",tone:ownTone||(fields?null:calTone(cal.status,cells))||calSourceTone(cal,fields?row.external:read(cal.source)),source:fields?row.external:cal.source?calResourceOf(read(cal.source)):null,external:!!fields,toneFromStatus:!ownTone&&!fields,resource:cal.
|
|
86
|
+
base={start:first*CAL_DAY,end:(endDay>first?endDay:first+1)*CAL_DAY}}else base={start,end:Number.isFinite(end)&&end>=start?end:start};const ownTone=cal.tone&&!fields?calSafeTone(String(read(cal.tone)??"").trim()):null;const series=value("series")||null;Object.assign(base,{key:row.id,row,allDay,marker:!allDay&&base.end===base.start,identity:fields?row.id:calNativeIdentity(row.id),seriesIdentity:fields?series:series&&calNativeIdentity(series),title:(fields?fields.title:cellText(cells[cal.title.id],cal.title,view.fmt))||row.id,subtitle:cal.subtitle&&!fields?cellText(cells[cal.subtitle.id],cal.subtitle,view.fmt):"",tone:ownTone||(fields?null:calTone(cal.status,cells))||calSourceTone(cal,fields?row.external:read(cal.source)),source:fields?row.external:cal.source?calResourceOf(read(cal.source)):null,external:!!fields,toneFromStatus:!ownTone&&!fields,resource:calResourceOf(value("resource")),resourceIds:calResourceIds(value("resourceIds")),color:cal.sources?.find(source=>source.id===(fields?row.external:read(cal.source)))?.color,background:cal.display?String(read(cal.display)||"").toLowerCase()==="background":false,rule:null,ruleError:null,seriesZone:allDay?CAL_UTC():fields?.seriesZone?calZone(fields.seriesZone):view.calSeriesZone,series,recurrenceId:calInstant(value("recurrenceId"))});const text=value("recurrence");if(typeof text==="string"&&text.trim()&&!base.series){try{const rec=calRec();if(!rec)throw new Error("CanopyX: the calendar recurrence part is not loaded.");base.rule=rec.calRule(text);Object.assign(base,rec.calExceptions(value("exceptions"),base.seriesZone))}
|
|
82
87
|
catch(error){base.ruleError=error}}}
|
|
83
88
|
CAL_BASES.set(row,{view,version:view.calVersion,base});return base}
|
|
84
89
|
function calSourceTone(cal,id){return id!=null&&cal.sources?.length?cal.sources.find(source=>source.id===String(id).trim())?.tone??null:null}
|
|
85
90
|
function calResourceOf(value){return typeof value==="string"&&value.trim()?value.trim():null}
|
|
86
|
-
function
|
|
87
|
-
|
|
91
|
+
function calResourceIds(value){if(value==null||value==="")return[];const list=typeof value==="string"?JSON.parse(value):value;if(!Array.isArray(list)||list.length>1000||list.some(id=>typeof id!=="string"||!id||id.isWellFormed&&!id.isWellFormed()))throw new Error("CanopyX: resourceIds is an array of at most 1,000 resource ids.");return[...new Set(list)]}
|
|
92
|
+
Object.assign(DataGrid.prototype,{calEventResources(event){return[...new Set([event.base?.resource??event.resource,...(event.resourceIds||event.base?.resourceIds||[])].filter(id=>id!=null))]},calHasResource(event,resource){const ids=this.calEventResources(event);return resource==null?!ids.length:ids.includes(resource)},calResourceValues(event,resource){const values=[],base=event.base,cal=this.cal;if(resource===undefined||resource===event.resource)return values;if(cal.resource&&base.resource===event.resource)values.push([cal.resource.id,resource]);if(cal.resourceIds&&base.resourceIds.includes(event.resource))values.push([cal.resourceIds.id,JSON.stringify([...new Set(base.resourceIds.map(id=>id===event.resource?resource:id).filter(id=>id!=null))])]);if(!values.length&&cal.resource)values.push([cal.resource.id,resource]);return values}});function calTone(column,cells){if(!column)return null;const cell=cells[column.id],raw=cellValue(cell),own=isCellObject(cell)&&typeof cell.tone==="string"?cell.tone:null;if(raw==null)return calSafeTone(own);return calSafeTone(column.presenter?gridPresenterTone(column.presenter,raw,own):own)}
|
|
93
|
+
function calOccurrences(view,rows,from,to,metadata){const out=[],overrides=new Map([...(metadata||[])].map(([id,starts])=>[calNativeIdentity(id),new Set(starts)])),bases=[],limit=view.cal.limit;for(const row of rows){const base=calBase(view,row);if(!base)continue;if(base.series&&Number.isFinite(base.recurrenceId)){if(!overrides.has(base.seriesIdentity))overrides.set(base.seriesIdentity,new Set());overrides.get(base.seriesIdentity).add(base.recurrenceId)}
|
|
88
94
|
bases.push(base)}
|
|
89
95
|
for(const base of bases){if(base.ruleError&&!view.calRuleErrors.has(base.key)){view.calRuleErrors.add(base.key);view.emit("error",{error:base.ruleError,id:base.key})}
|
|
90
96
|
if(!base.rule){const span=view.calDisplaySpan(base);if(calOverlaps(span.start,span.end,from,to))out.push(calOccurrence(view,base,base.start,false));continue}
|
|
91
|
-
const duration=base.end-base.start,moved=overrides.get(base.
|
|
97
|
+
const duration=base.end-base.start,moved=overrides.get(base.identity),excluded=moved?new Set([...base.excluded,...moved]):base.excluded;base.overridden=!!moved;const first=base.allDay?view.calZone.day(from)*CAL_DAY:from,last=base.allDay?(view.calZone.day(to-1)+ 1)*CAL_DAY:to;const starts=calRec().calExpand(base.rule,base.start,duration,base.seriesZone,first,last,excluded,base.excludedDays,limit+1);for(const start of starts){const event=calOccurrence(view,base,start,true),span=view.calDisplaySpan(event);if(calOverlaps(span.start,span.end,from,to))out.push(event)}}
|
|
92
98
|
out.sort((a,b)=>a.start-b.start||(b.end-b.start)-(a.end-a.start)||(a.id<b.id?-1:a.id>b.id?1:0));if(out.length>limit){out.length=limit;out.truncated=true}
|
|
93
99
|
return out}
|
|
94
100
|
function calOverlaps(start,end,from,to){return end>start?start<to&&end>from:start>=from&&start<to}
|
|
95
|
-
function calOccurrence(view,base,start,recurring){const end=start +(base.end-base.start),zone=base.allDay?CAL_UTC():view.calZone;const sd=zone.day(start),ed=end>start?zone.day(end-1):sd;return{id:recurring?base.
|
|
101
|
+
function calOccurrence(view,base,start,recurring){const end=start +(base.end-base.start),zone=base.allDay?CAL_UTC():view.calZone;const sd=zone.day(start),ed=end>start?zone.day(end-1):sd;return{id:recurring?base.identity+"@"+calIso(start):base.identity,key:base.key,base,row:base.row,start,end,allDay:base.allDay,marker:base.marker,sd,ed,recurring,occurrence:recurring?start:null,background:base.background,resource:base.resource,title:base.title,subtitle:base.subtitle,tone:base.tone,color:base.color,resourceIds:base.resourceIds}}
|
|
96
102
|
function calStaticRanges(view,from,to){const out=[],inverse=new Map();view.cal.ranges.forEach((range,index)=>{const starts=range.recurrence?calRec().calExpand(calRec().calRule(range.recurrence),range.start,range.end-range.start,view.calSeriesZone,from,to,new Set(),new Set(),range.inverse?20000:1000):
|
|
97
103
|
calOverlaps(range.start,range.end,from,to)?[range.start]:[];if(range.inverse&&!inverse.has(range.resource??""))inverse.set(range.resource??"",{range,index,spans:[]});for(const start of starts){const end=start+range.end-range.start;if(range.inverse){inverse.get(range.resource??"").spans.push([Math.max(start,from),Math.min(end,to)]);continue}
|
|
98
104
|
out.push({id:"range-"+index+"-"+start,start,end,sd:view.calZone.day(start),ed:view.calZone.day(end-1),label:range.label,tone:range.tone,resource:range.resource,kind:range.kind})}});for(const{range,index,spans}of inverse.values()){const add=(start,end)=>{if(end>start)out.push({id:"range-"+index+"-out-"+start,start,end,sd:view.calZone.day(start),ed:view.calZone.day(end-1),label:range.label,tone:range.tone,resource:range.resource,kind:range.kind,inverse:true})};let at=from;for(const[start,end]of spans.sort((a,b)=>a[0]- b[0])){add(at,start);at=Math.max(at,end)}
|
|
@@ -118,24 +124,28 @@ zoom.addEventListener("change",()=>this.setCalendarZoom(zoom.value));if(this.cal
|
|
|
118
124
|
const print=this.cal.print?button(this.labels.gridPrint||"Print","print",()=>global.print(),"print"):null;bar.append(nav,this.calTitle,h("span","cx-spacer"),jumpBox,zoom,views,select,...(print?[print]:[]));return bar},calViewName(view){const labels=this.calLabels;return view==="days"?fill(labels.calendarDays,{count:this.calDaysCount()}):labels["calendar"+view[0].toUpperCase()+ view.slice(1)]},calDaysCount(){return this.calPhone&&this.cal.days>3?3:this.cal.days},calSyncBar(announce){const view=this.calView,range=this.calRange;for(const[name,item]of this.calViewButtons){item.setAttribute("aria-pressed",String(name===view));item.classList.toggle("active",name===view)}
|
|
119
125
|
this.calViewSelect.value=view;this.calZoomSelect.hidden=view!=="timeline"||this.cal.zooms.length<2;this.calZoomSelect.value=this.calZoom;const title=this.calRangeTitle();setText(this.calTitle,title);this.calJump.value=calDayText(this.calDate);if(this.calJumpButton){setText(this.calJumpButton,this.calFmt.date(this.calDate));this.calJumpButton.setAttribute("aria-label",this.calLabels.calendarGoTo+", "+this.calFmt.date(this.calDate));this.calJumpButton.title=this.calLabels.calendarGoTo}
|
|
120
126
|
const today=this.calZone.day(Date.now());this.calTodayButton.setAttribute("aria-disabled",String(today>=range.firstDay&&today<=range.lastDay||!this.calInValid({firstDay:today,lastDay:today})));if(this.cal.validRange)for(const[button,direction]of[[this.calPrev,-1],[this.calNextButton,1]])button.setAttribute("aria-disabled",String(!this.calInValid(this.calRangeOf(view,this.calStepDate(direction)))));if(announce)this.calAnnounce(fill(this.calLabels.calendarShowing,{view:this.calViewName(view),range:title}))},calRangeTitle(){const fmt=this.calFmt,range=this.calRange;switch(this.calView){case "month":case "mini":return fmt.monthTitle(this.calDate);case "agenda":{const span=this.cal.agendaSpan;return span==="month"?fmt.monthTitle(this.calDate):span==="year"?fmt.year(this.calDate):span==="day"?fmt.longDay(range.firstDay):fmt.dayRange(range.firstDay,range.lastDay)}
|
|
121
|
-
case "months":return fmt.monthRange(range.firstDay,range.lastDay);case "year":return fmt.year(this.calDate);case "day":case "dayList":return this.calPhone?fmt.shortDay(range.firstDay)+ ", "+fmt.year(range.firstDay):fmt.longDay(range.firstDay);case "timeline":return this.calTimelineTitle?this.calTimelineTitle():fmt.dayRange(range.firstDay,range.lastDay);default:return fmt.dayRange(range.firstDay,range.lastDay)}},calAnnounce(text){clearTimeout(this.calAnnounceTimer);this.calAnnounceTimer=setTimeout(()=>{if(!this.destroyed)setText(this.calLive,text)},150)}});const CAL_ROW_COPIES=new WeakMap();Object.assign(DataGrid.prototype,{calTimeText(event){if(event.allDay)return this.calLabels.calendarAllDay;const fmt=this.calFmt,labels=this.calLabels;if(event.marker)return fmt.time(event.start);if(event.ed>event.sd)return fill(labels.calendarTimeRange,{start:fmt.shortDay(event.sd)+ " "+fmt.time(event.start),end:fmt.shortDay(event.ed)+ " "+fmt.time(event.end)});return fill(labels.calendarTimeRange,{start:fmt.time(event.start),end:fmt.time(event.end)})},calStatusText(event){const column=this.cal.status;return column?cellText(event.row.cells[column.id],column,this.fmt):""},calEventName(event){const parts=[event.title,event.allDay&&event.ed>event.sd?this.calFmt.dayRange(event.sd,event.ed):this.calTimeText(event)];const status=this.calStatusText(event);if(status)parts.push(status);if(event.subtitle)parts.push(event.subtitle);if(event.
|
|
122
|
-
element.
|
|
127
|
+
case "months":return fmt.monthRange(range.firstDay,range.lastDay);case "year":return fmt.year(this.calDate);case "day":case "dayList":return this.calPhone?fmt.shortDay(range.firstDay)+ ", "+fmt.year(range.firstDay):fmt.longDay(range.firstDay);case "timeline":return this.calTimelineTitle?this.calTimelineTitle():fmt.dayRange(range.firstDay,range.lastDay);default:return fmt.dayRange(range.firstDay,range.lastDay)}},calAnnounce(text){clearTimeout(this.calAnnounceTimer);this.calAnnounceTimer=setTimeout(()=>{if(!this.destroyed)setText(this.calLive,text)},150)}});const CAL_ROW_COPIES=new WeakMap();Object.assign(DataGrid.prototype,{calTimeText(event){if(event.allDay)return this.calLabels.calendarAllDay;const fmt=this.calFmt,labels=this.calLabels;if(event.marker)return fmt.time(event.start);if(event.ed>event.sd)return fill(labels.calendarTimeRange,{start:fmt.shortDay(event.sd)+ " "+fmt.time(event.start),end:fmt.shortDay(event.ed)+ " "+fmt.time(event.end)});return fill(labels.calendarTimeRange,{start:fmt.time(event.start),end:fmt.time(event.end)})},calStatusText(event){const column=this.cal.status;return column?cellText(event.row.cells[column.id],column,this.fmt):""},calEventName(event){const parts=[event.title,event.allDay&&event.ed>event.sd?this.calFmt.dayRange(event.sd,event.ed):this.calTimeText(event)];const status=this.calStatusText(event);if(status)parts.push(status);if(event.subtitle)parts.push(event.subtitle);if(this.calResourceName)parts.push(...this.calEventResources(event).map(id=>this.calResourceName(id)));if(this.calEventResources(event).length>1)parts.push(calCount(this,this.calLabels.calendarSharedResources,this.calEventResources(event).length,{resources:this.calLabels.calendarResources.toLocaleLowerCase(this.lang)}));if(event.recurring)parts.push(this.calLabels.calendarRepeats);return parts.filter(Boolean).join(", ")},calDisposeEventRenderers(container){const slots=this.calHostSlots;if(!container)this.calHostSlots=null;for(const slot of slots||[])if(!container||container.contains(slot.content)){slots.delete(slot);disposeHostSlot(slot)}},calEventElement(event,kind){const element=h("button","cx-cal-ev cx-cal-ev-"+kind +(event.marker?" cx-cal-marker":"")+(event.allDay?" cx-cal-allday":""));element.type="button";element.tabIndex=-1;element.dataset.cxEvent=event.id;element.dataset.cxRecord=event.key;element._calEvent=event;if(event.tone){element.dataset.cxTone=event.tone;element.style.setProperty("--cx-ev",event.tone==="neutral"?"var(--hfx-color-status-neutral-fg,var(--hfx-color-text-muted,light-dark(#626770,#8f949d)))":toneVariable(event.tone,false,"var(--hfx-color-accent,light-dark(#11776f,#37c9b8))"));element.style.setProperty("--cx-ev-bg",toneVariable(event.tone,true,"color-mix(in srgb,var(--cx-ev) 14%,var(--hfx-color-surface,light-dark(#ffffff,#16181c)))"))}
|
|
128
|
+
if(event.color){element.style.setProperty("--cx-ev",event.color);element.style.setProperty("--cx-ev-bg","color-mix(in srgb,var(--cx-ev) 14%,var(--hfx-color-surface,light-dark(#ffffff,#16181c)))")}
|
|
129
|
+
element.setAttribute("aria-label",this.calEventName(event));const assignments=this.calEventResources(event);if(assignments.length>1){element.title=this.calEventName(event);const mark=(kind,on)=>{this[kind]=on?event.id:this[kind]===event.id?null:this[kind];for(const card of this.calRoot.querySelectorAll(".cx-cal-ev"))card.classList.toggle("cx-cal-shared-active",card.dataset.cxEvent===this.calSharedHover||card.dataset.cxEvent===this.calSharedFocus)};element.addEventListener("pointerenter",()=>mark("calSharedHover",true));element.addEventListener("pointerleave",()=>mark("calSharedHover",false));element.addEventListener("focus",()=>mark("calSharedFocus",true));element.addEventListener("blur",()=>mark("calSharedFocus",false))}
|
|
130
|
+
if(!event.base?.external&&this.linkHighlight?.has(event.key))element.dataset.cxLinked="true";const template=this.opts.calendar?.render;if(typeof template==="function"){let copy=CAL_ROW_COPIES.get(event.row);if(copy?.version!==this.calVersion){copy={version:this.calVersion,row:gridFreeze(JSON.parse(JSON.stringify(event.row)))};CAL_ROW_COPIES.set(event.row,copy)}
|
|
123
131
|
const row=copy.row;try{if(template({event:this.calPublicEvent(event),row,element,kind,labels:this.calLabels})===true)return element}
|
|
124
132
|
catch(error){this.emit("error",{error});element.textContent=""}}
|
|
125
133
|
const time=h("span","cx-cal-ev-time"),title=h("span","cx-cal-ev-title");if(!event.allDay&&kind!=="bar"&&kind!=="lane"){setText(time,kind==="item"?this.calTimeText(event):this.calFmt.time(event.start));element.appendChild(time)}
|
|
126
|
-
setText(title,event.title);element.appendChild(title);if(
|
|
134
|
+
setText(title,event.title);element.appendChild(title);if(assignments.length>1){const shared=h("span","cx-cal-shared");setText(shared,calCount(this,this.calLabels.calendarSharedResources,assignments.length,{resources:this.calLabels.calendarResources.toLocaleLowerCase(this.lang)}));shared.setAttribute("aria-hidden","true");element.appendChild(shared)}
|
|
135
|
+
if(kind==="block"||kind==="item"||kind==="lane"){if(event.subtitle){const subtitle=h("span","cx-cal-ev-sub");setText(subtitle,event.subtitle);element.appendChild(subtitle)}
|
|
127
136
|
const column=this.cal.status;if(column&&(kind!=="lane")&&cellValue(event.row.cells[column.id])!=null){const statusClass="cx-cal-ev-status" +(event.base?.toneFromStatus?" cx-cal-status-tone":"");let status=h("span",statusClass);let drawn=false;if(column.render){const slot={content:status},context=new HostCellContext(slot,column.id,undefined,undefined,this.columns.indexOf(column));(this.calHostSlots||=new Set()).add(slot);try{drawn=column.render(event.row,status,context)===true}
|
|
128
137
|
catch(error){disposeHostSlot(slot);this.calHostSlots.delete(slot);status=h("span",statusClass);this.emit("error",{error})}}
|
|
129
138
|
if(!drawn){const text=h("span");setText(text,this.calStatusText(event));status.appendChild(text)}
|
|
130
139
|
element.appendChild(status)}}
|
|
131
|
-
if(event.recurring)element.appendChild(calIcon("repeat"));return element},calPublicEvent(event){return gridFreeze({id:event.id,record:event.key,start:calIso(event.start),end:calIso(event.end),allDay:event.allDay,title:event.title,resource:event.resource,recurring:event.recurring,source:event.base?.source??null,external:!!event.base?.external})},async calOpenEvent(event,keyboard,returnFocus){if(event.base?.external)return this.calShowDetails(event,this.
|
|
132
|
-
return this.runGridHook(signal=>this.opts.onDetails({row,query:this.acceptedQuery,signal,returnFocus,occurrence:event.recurring?calIso(event.start):null}),this.dataLifetime.signal);if(this.cal.details==="popover")return this.calShowDetails(event,this.
|
|
140
|
+
if(event.recurring)element.appendChild(calIcon("repeat"));return element},calPublicEvent(event){return gridFreeze({id:event.id,record:event.key,start:calIso(event.start),end:calIso(event.end),allDay:event.allDay,title:event.title,resource:event.resource,recurring:event.recurring,resourceIds:this.calEventResources(event),source:event.base?.source??null,external:!!event.base?.external})},async calOpenEvent(event,keyboard,returnFocus){if(event.base?.external)return this.calShowDetails(event,this.calEventTarget(event.id,returnFocus)||returnFocus);const row=gridFreeze(JSON.parse(JSON.stringify(event.row)));if(!this.emit("rowActivate",{id:event.key,row,keyboard:!!keyboard,occurrence:event.recurring?calIso(event.start):null}))return;if(typeof this.opts.onDetails==="function")
|
|
141
|
+
return this.runGridHook(signal=>this.opts.onDetails({row,query:this.acceptedQuery,signal,returnFocus,occurrence:event.recurring?calIso(event.start):null}),this.dataLifetime.signal);if(this.cal.details==="popover")return this.calShowDetails(event,this.calEventTarget(event.id,returnFocus)||returnFocus);await this.requirePart("filter-editor");if(this.recordNeeded(event.row))await this.loadRecordPanel();if(this.destroyed)return;const dialog=this.openGridDrawer(event.row,null,returnFocus);const id=event.id;dialog?.addEventListener("close",()=>requestAnimationFrame(()=>{if(this.destroyed||this.calRoot.contains(document.activeElement)||document.activeElement&&document.activeElement!==document.body&&document.activeElement!==this.grid)return;(this.calBody.querySelector('[data-cx-event="'+CSS.escape(id)+ '"]')||this.calFocusTarget())?.focus({preventScroll:true})}),{once:true});return dialog},async openCalendarEvent(id){const event=this.calEvents.find(item=>item.id===id)||this.calEvents.find(item=>item.key===id);if(!event)throw new Error("CanopyX: the calendar does not show event "+id+".");return this.calOpenEvent(event,false,document.activeElement)},calOpenMore(day,events,anchor){this.calCloseMore();const labels=this.calLabels,panel=this.calMore=h("div","cx-cal-more"),title=h("h3","cx-cal-more-h"),list=h("div","cx-cal-more-list");panel.setAttribute("role","dialog");panel.setAttribute("aria-modal","false");title.id=this.id+"-cal-more";panel.setAttribute("aria-labelledby",title.id);setText(title,fill(labels.calendarDayEvents,{date:this.calFmt.longDay(day)}));const close=gridButton("",()=>this.calCloseMore(true));close.classList.add("cx-btn","icon","ghost","cx-cal-more-close");close.setAttribute("aria-label",this.labels.gridCloseRecord);close.appendChild(gridIcon("close"));const head=h("div","cx-cal-more-top");head.append(title,close);for(const event of events)list.appendChild(this.calEventElement(event,"chip"));panel.append(head,list);panel._calAnchor=anchor;this.calRoot.appendChild(panel);const box=anchor.getBoundingClientRect(),frame=this.calRoot.getBoundingClientRect();const width=Math.min(300,frame.width-16);panel.style.width=width+"px";panel.style.left=Math.max(8,Math.min(box.left-frame.left-8,frame.width-width-8))+ "px";panel.style.top=Math.max(8,Math.min(box.top-frame.top-4,frame.height-panel.offsetHeight-8))+ "px";panel.addEventListener("keydown",event=>{const items=[...list.children],at=items.indexOf(document.activeElement);if(event.key==="Escape"){event.preventDefault();event.stopPropagation();this.calCloseMore(true)}
|
|
133
142
|
else if(event.key==="ArrowDown"||event.key==="ArrowUp"){event.preventDefault();items[Math.max(0,Math.min(items.length-1,at +(event.key==="ArrowDown"?1:-1)))]?.focus()}
|
|
134
|
-
else if(event.key==="Tab"){event.preventDefault();(document.activeElement===close?items[0]:close).focus()}});panel.addEventListener("click",event=>{const element=event.target.closest(".cx-cal-ev");if(element)this.calOpenEvent(element._calEvent,event.detail===0,element).catch(error=>this.emit("error",{error}))});this.calMoreOutside=event=>{if(!panel.contains(event.target)&&event.target!==anchor)this.calCloseMore(false)};document.addEventListener("pointerdown",this.calMoreOutside,true);(list.firstChild||close).focus();list.firstChild&&(list.firstChild.tabIndex=0)},calCloseMore(restore){const panel=this.calMore;if(!panel)return;this.calMore=null;document.removeEventListener("pointerdown",this.calMoreOutside,true);panel.remove();if(restore&&panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true})},calMarkHighlights(){const keys=this.linkHighlight;let count=0;for(const element of this.calBody.querySelectorAll(".cx-cal-ev")){const on
|
|
135
|
-
this.host.classList.toggle("cx-cal-has-linked",!!keys?.size);if(keys?.size)this.calAnnounce(calCount(this,this.calLabels.calendarHighlighted,count))}});const CAL_HOVER_DELAY=600;Object.assign(DataGrid.prototype,{calDetailsContent(event,titleId){const title=h("h3","cx-cal-det-title"),when=h("p","cx-cal-det-when"),parts=[title,when];if(titleId)title.id=titleId;setText(title,event.title);setText(when,this.calTimeText(event));const status=this.calStatusText(event),resource=event.
|
|
143
|
+
else if(event.key==="Tab"){event.preventDefault();(document.activeElement===close?items[0]:close).focus()}});panel.addEventListener("click",event=>{const element=event.target.closest(".cx-cal-ev");if(element)this.calOpenEvent(element._calEvent,event.detail===0,element).catch(error=>this.emit("error",{error}))});this.calMoreOutside=event=>{if(!panel.contains(event.target)&&event.target!==anchor)this.calCloseMore(false)};document.addEventListener("pointerdown",this.calMoreOutside,true);(list.firstChild||close).focus();list.firstChild&&(list.firstChild.tabIndex=0)},calCloseMore(restore){const panel=this.calMore;if(!panel)return;this.calMore=null;document.removeEventListener("pointerdown",this.calMoreOutside,true);panel.remove();if(restore&&panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true})},calMarkHighlights(){const keys=this.linkHighlight;let count=0;for(const element of this.calBody.querySelectorAll(".cx-cal-ev")){const on=!element._calEvent?.base?.external&&!!keys?.has(element.dataset.cxRecord);if(on)count++;if(on)element.dataset.cxLinked="true";else delete element.dataset.cxLinked}
|
|
144
|
+
this.host.classList.toggle("cx-cal-has-linked",!!keys?.size);if(keys?.size)this.calAnnounce(calCount(this,this.calLabels.calendarHighlighted,count))}});const CAL_HOVER_DELAY=600;Object.assign(DataGrid.prototype,{calDetailsContent(event,titleId){const title=h("h3","cx-cal-det-title"),when=h("p","cx-cal-det-when"),parts=[title,when];if(titleId)title.id=titleId;setText(title,event.title);setText(when,this.calTimeText(event));const status=this.calStatusText(event),resource=this.calEventResources(event).map(id=>this.calResourceName?this.calResourceName(id):id).join(", ");const fields=event.base?.external?event.row.fields:null,sourceName=fields?this.cal.sources.find(source=>source.id===event.base.source)?.label:"";const facts=[status,resource,event.subtitle,fields?.location,sourceName].filter(Boolean);if(facts.length){const line=h("p","cx-cal-det-facts");setText(line,facts.join(" · "));parts.push(line)}
|
|
136
145
|
const description=fields?fields.description||"":this.cal.description?cellText(event.row.cells[this.cal.description.id],this.cal.description,this.fmt):"";if(description){const text=h("p","cx-cal-det-desc");setText(text,description);parts.push(text)}
|
|
137
146
|
return parts},calPlace(panel,anchor){this.calRoot.appendChild(panel);const box=anchor.getBoundingClientRect(),frame=this.calRoot.getBoundingClientRect(),width=Math.min(320,frame.width-16);panel.style.width=width+"px";const rtl=getComputedStyle(this.calRoot).direction==="rtl";const right=box.right-frame.left+6,left=box.left-frame.left-width-6;let x=rtl?(left>=8?left:right):(right+width<=frame.width-8?right:left);x=Math.max(8,Math.min(x,frame.width-width-8));panel.style.left=x+"px";panel.style.top=Math.max(8,Math.min(box.top-frame.top,frame.height-panel.offsetHeight-8))+ "px"},calShowDetails(event,anchor){this.calCloseDetails(false);this.calHideHover();const panel=this.calDetails=h("div","cx-cal-details"),close=gridButton("",()=>this.calCloseDetails(true)),head=h("div","cx-cal-det-top");const titleId=this.id+"-cal-det";panel.setAttribute("role","dialog");panel.setAttribute("aria-modal","false");panel.setAttribute("aria-labelledby",titleId);close.classList.add("cx-btn","icon","ghost","cx-cal-det-close");close.setAttribute("aria-label",this.calLabels.calendarClose);close.appendChild(gridIcon("close"));const[title,...rest]=this.calDetailsContent(event,titleId);head.append(title,close);panel.append(head,...rest);panel._calAnchor=anchor;this.calPlace(panel,anchor||this.calBody);const description=panel.querySelector(".cx-cal-det-desc");if(description&&description.scrollHeight>description.clientHeight+1)description.tabIndex=0;panel.addEventListener("keydown",key=>{if(key.key==="Escape"){key.preventDefault();key.stopPropagation();this.calCloseDetails(true)}
|
|
138
|
-
else if(key.key==="Tab"){key.preventDefault();const stops=[close,...panel.querySelectorAll(".cx-cal-det-desc[tabindex]")],at=stops.indexOf(document.activeElement);stops[(at +(key.shiftKey?stops.length-1:1))%stops.length].focus()}});this.calDetailsOutside=press=>{if(!panel.contains(press.target)&&press.target!==anchor)this.calCloseDetails(false)};document.addEventListener("pointerdown",this.calDetailsOutside,true);close.focus({preventScroll:true});return panel},calCloseDetails(restore){const panel=this.calDetails;if(!panel)return;this.calDetails=null;document.removeEventListener("pointerdown",this.calDetailsOutside,true);panel.remove();if(restore&&panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true})},calHoverOver(pointer){if(!this.cal.hover||pointer.pointerType==="touch"||this.calDrag||this.calMoving||this.calDetails||this.calMenu)return;const element=pointer.target.closest?.(".cx-cal-ev");if(!element?._calEvent||element===this.calHoverAnchor)return;this.calHideHover();this.calHoverAnchor=element;this.calHoverTimer=setTimeout(()=>{if(this.destroyed||!element.isConnected||this.calDrag||this.calDetails)return;const card=this.calHover=h("div","cx-cal-hover");card.setAttribute("aria-hidden","true");card.append(...this.calDetailsContent(element._calEvent));this.calPlace(card,element)},CAL_HOVER_DELAY)},calHoverOut(pointer){if(this.calHoverAnchor&&!this.calHoverAnchor.contains(pointer.relatedTarget))this.calHideHover()},calHideHover(){clearTimeout(this.calHoverTimer);this.calHoverAnchor=null;this.calHover?.remove();this.calHover=null}});Object.assign(DataGrid.prototype,{calMenuContext(target,x){const element=target.closest?.(".cx-cal-ev"),lane=!element&&x!=null&&target.closest?.(".cx-tl-lane");const cell=lane?[...lane.querySelectorAll(".cx-tl-cell")].find(item=>{const box=item.getBoundingClientRect();return x>=box.left&&x<box.right}):target.closest?.("[data-cx-day]");if(element?._calEvent)return{kind:"event",target:element,event:this.calPublicEvent(element._calEvent),raw:element._calEvent};if(!cell)return null;const minutes=cell.dataset.cxMinutes!==undefined?Number(cell.dataset.cxMinutes):null;return{kind:cell.classList.contains("cx-tl-cell")?"resource":minutes===null?"day":"slot",target:cell,day:calDayText(Number(cell.dataset.cxDay)),minutes,resource:this.calCellResource(cell)}},calMenuItems(context){const labels=this.calLabels,items=[];if(context.kind==="event"){items.push({id:"open",label:labels.calendarOpen,run:()=>this.calOpenEvent(context.raw,true,context.target)});if(
|
|
147
|
+
else if(key.key==="Tab"){key.preventDefault();const stops=[close,...panel.querySelectorAll(".cx-cal-det-desc[tabindex]")],at=stops.indexOf(document.activeElement);stops[(at +(key.shiftKey?stops.length-1:1))%stops.length].focus()}});this.calDetailsOutside=press=>{if(!panel.contains(press.target)&&press.target!==anchor)this.calCloseDetails(false)};document.addEventListener("pointerdown",this.calDetailsOutside,true);close.focus({preventScroll:true});return panel},calCloseDetails(restore){const panel=this.calDetails;if(!panel)return;this.calDetails=null;document.removeEventListener("pointerdown",this.calDetailsOutside,true);panel.remove();if(restore&&panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true})},calHoverOver(pointer){if(!this.cal.hover||pointer.pointerType==="touch"||this.calDrag||this.calMoving||this.calDetails||this.calMenu)return;const element=pointer.target.closest?.(".cx-cal-ev");if(!element?._calEvent||element===this.calHoverAnchor)return;this.calHideHover();this.calHoverAnchor=element;this.calHoverTimer=setTimeout(()=>{if(this.destroyed||!element.isConnected||this.calDrag||this.calDetails)return;const card=this.calHover=h("div","cx-cal-hover");card.setAttribute("aria-hidden","true");card.append(...this.calDetailsContent(element._calEvent));this.calPlace(card,element)},CAL_HOVER_DELAY)},calHoverOut(pointer){if(this.calHoverAnchor&&!this.calHoverAnchor.contains(pointer.relatedTarget))this.calHideHover()},calHideHover(){clearTimeout(this.calHoverTimer);this.calHoverAnchor=null;this.calHover?.remove();this.calHover=null}});Object.assign(DataGrid.prototype,{calMenuContext(target,x){const element=target.closest?.(".cx-cal-ev"),lane=!element&&x!=null&&target.closest?.(".cx-tl-lane");const cell=lane?[...lane.querySelectorAll(".cx-tl-cell")].find(item=>{const box=item.getBoundingClientRect();return x>=box.left&&x<box.right}):target.closest?.("[data-cx-day]");if(element?._calEvent)return{kind:"event",target:element,event:this.calPublicEvent(element._calEvent),raw:element._calEvent};if(!cell)return null;const minutes=cell.dataset.cxMinutes!==undefined?Number(cell.dataset.cxMinutes):null;return{kind:cell.classList.contains("cx-tl-cell")?"resource":minutes===null?"day":"slot",target:cell,day:calDayText(Number(cell.dataset.cxDay)),minutes,resource:this.calCellResource(cell)}},calMenuItems(context){const labels=this.calLabels,items=[];if(context.kind==="event"){items.push({id:"open",label:labels.calendarOpen,run:()=>this.calOpenEvent(context.raw,true,context.target)});if(context.raw.recurring&&this.cal.editable&&this.cal.series?.editable&&this.cal.recurrenceId?.editable&&this.calCanEdit?.(context.raw,false))
|
|
148
|
+
items.push({id:"occurrence",label:labels.calendarEditOccurrence,run:()=>this.editCalendarOccurrence(context.raw.id)});if(this.calStartMove&&this.cal.editable&&(this.calCanEdit(context.raw,false)||this.calCanEdit(context.raw,true)))items.push({id:"move",label:labels.calendarMove,run:()=>this.calStartMove(context.target)})}else if(this.cal.create&&this.calCreateFromClick){items.push({id:"new",label:labels.calendarNewHere,run:()=>this.calCreateFromClick(context.target)})}
|
|
139
149
|
const hook=this.opts.calendar?.menu;if(typeof hook!=="function")return items;const own={kind:context.kind,event:context.event,day:context.day,minutes:context.minutes,resource:context.resource};let result;try{result=hook(Object.freeze(own),items.map(({id,label})=>Object.freeze({id,label})))}catch(error){this.emit("error",{error});return items}
|
|
140
150
|
if(result===null||result===false)return[];if(!Array.isArray(result))return items;return result.slice(0,30).map(item=>{const kept=item&&items.find(entry=>entry.id===item.id&&!item.run);if(kept)return kept;return item&&typeof item.label==="string"&&item.label.trim()&&typeof item.run==="function"?{id:item.id??null,label:item.label.slice(0,200),run:()=>item.run(Object.freeze(own)),disabled:item.disabled===true}:null}).filter(Boolean)},calOpenMenu(target,x,y){if(!this.cal.menu)return false;const context=this.calMenuContext(target,x);if(context?.target.classList.contains("cx-cal-invalid"))return false;if(!context)return false;const items=this.calMenuItems(context);if(!items.length)return false;this.calCloseMenu(false);this.calHideHover();const menu=this.calMenu=h("div","cx-cal-menu"),name=context.kind==="event"?context.raw.title:context.kind==="resource"?this.calResourceName?.(context.resource)??"":this.calFmt.longDay(calParseDay(context.day));menu.setAttribute("role","menu");menu.setAttribute("aria-label",fill(this.calLabels.calendarActions,{title:name}));const buttons=items.map(item=>{const button=h("button","cx-cal-menu-item");button.type="button";button.setAttribute("role","menuitem");button.tabIndex=-1;setText(button,item.label);if(item.disabled)button.setAttribute("aria-disabled","true");button.addEventListener("click",()=>{if(item.disabled)return;this.calCloseMenu(true);Promise.resolve().then(()=>item.run()).catch(error=>this.emit("error",{error}))});menu.appendChild(button);return button});menu._calReturn=target;menu.addEventListener("contextmenu",event=>event.preventDefault());this.calRoot.appendChild(menu);const frame=this.calRoot.getBoundingClientRect(),box=target.getBoundingClientRect(),width=menu.offsetWidth,height=menu.offsetHeight;const left=x!=null?x-frame.left:box.left-frame.left+Math.min(box.width,24),top=y!=null?y-frame.top:box.top-frame.top+Math.min(box.height,24);menu.style.left=Math.max(4,Math.min(left,frame.width-width-4))+ "px";menu.style.top=Math.max(4,Math.min(top,frame.height-height-4))+ "px";menu.addEventListener("keydown",key=>{const at=buttons.indexOf(document.activeElement);if(key.key==="Escape"||key.key==="Tab"){key.preventDefault();key.stopPropagation();this.calCloseMenu(true)}
|
|
141
151
|
else if(key.key==="ArrowDown"){key.preventDefault();buttons[(at+1)%buttons.length].focus()}
|
|
@@ -143,13 +153,21 @@ else if(key.key==="ArrowUp"){key.preventDefault();buttons[(at+buttons.length-1)%
|
|
|
143
153
|
else if(key.key==="Home"){key.preventDefault();buttons[0].focus()}
|
|
144
154
|
else if(key.key==="End"){key.preventDefault();buttons[buttons.length-1].focus()}});this.calMenuOutside=press=>{if(!menu.contains(press.target))this.calCloseMenu(false)};document.addEventListener("pointerdown",this.calMenuOutside,true);buttons[0].focus({preventScroll:true});return true},calCloseMenu(restore){const menu=this.calMenu;if(!menu)return;this.calMenu=null;document.removeEventListener("pointerdown",this.calMenuOutside,true);menu.remove();if(restore&&menu._calReturn?.isConnected)menu._calReturn.focus({preventScroll:true})},calMenuStart(){if(!this.cal.menu)return;const body=this.calBody,signal=this.lifetime.signal;let press=null;const cancel=()=>{if(press){clearTimeout(press.timer);press=null;this.calPressTimer=0}};body.addEventListener("contextmenu",event=>{cancel();if(this.calMenu&&Date.now()-(this.calMenuOpenedAt||0)<800){event.preventDefault();return}
|
|
145
155
|
if(event.shiftKey||this.calDrag?.started||this.calMoving)return;const keyboard=event.button!==2&&event.clientX===0&&event.clientY===0;if(this.calOpenMenu(event.target,keyboard?null:event.clientX,keyboard?null:event.clientY)){event.preventDefault();this.calMenuOpenedAt=Date.now()}},{signal});body.addEventListener("pointerdown",event=>{cancel();if(this.calPressSuppress){this.calPressSuppress=false;this.calSuppressClick=false}
|
|
146
|
-
if(event.pointerType!=="touch")return;press={x:event.clientX,y:event.clientY,target:event.target,timer:setTimeout(()=>{const at=press;press=null;if(at&&!this.calDrag?.started&&Date.now()-(this.calMenuOpenedAt||0)>800&&this.calOpenMenu(at.target,at.x,at.y)){this.calMenuOpenedAt=Date.now();this.calSuppressClick=true;this.calPressSuppress=true}},600)};this.calPressTimer=press.timer},{signal,passive:true});body.addEventListener("pointermove",event=>{if(press&&Math.hypot(event.clientX-press.x,event.clientY-press.y)>8)cancel()},{signal,passive:true});for(const type of["pointerup","pointercancel","scroll"])body.addEventListener(type,cancel,{signal,passive:true,capture:type==="scroll"})},calMenuKey(event){if(!this.cal.menu||!(event.key==="ContextMenu"||event.key==="F10"&&event.shiftKey))return false;if(!this.calOpenMenu(event.target))return false;event.preventDefault();this.calMenuOpenedAt=Date.now();return true}});Object.assign(DataGrid.prototype,{calBuildLegend(){const sources=this.cal.sources;this.calHiddenSources=new Set(sources.filter(source=>source.hidden).map(source=>source.id));if(!sources.length)return null;const legend=this.calLegend=h("div","cx-cal-legend");legend.setAttribute("role","group");legend.setAttribute("aria-label",this.calLabels.calendarSources);for(const source of sources){const button=h("button","cx-cal-src"),swatch=h("i","cx-cal-src-swatch"),text=h("span");button.type="button";button.dataset.cxSource=source.id;button.setAttribute("aria-pressed",String(!this.calHiddenSources.has(source.id)));if(source.tone)button.style.setProperty("--cx-ev","var(--
|
|
156
|
+
if(event.pointerType!=="touch")return;press={x:event.clientX,y:event.clientY,target:event.target,timer:setTimeout(()=>{const at=press;press=null;if(at&&!this.calDrag?.started&&Date.now()-(this.calMenuOpenedAt||0)>800&&this.calOpenMenu(at.target,at.x,at.y)){this.calMenuOpenedAt=Date.now();this.calSuppressClick=true;this.calPressSuppress=true}},600)};this.calPressTimer=press.timer},{signal,passive:true});body.addEventListener("pointermove",event=>{if(press&&Math.hypot(event.clientX-press.x,event.clientY-press.y)>8)cancel()},{signal,passive:true});for(const type of["pointerup","pointercancel","scroll"])body.addEventListener(type,cancel,{signal,passive:true,capture:type==="scroll"})},calMenuKey(event){if(!this.cal.menu||!(event.key==="ContextMenu"||event.key==="F10"&&event.shiftKey))return false;if(!this.calOpenMenu(event.target))return false;event.preventDefault();this.calMenuOpenedAt=Date.now();return true}});Object.assign(DataGrid.prototype,{calBuildLegend(){const sources=this.cal.sources;this.calHiddenSources=new Set(sources.filter(source=>source.hidden).map(source=>source.id));if(!sources.length)return null;const legend=this.calLegend=h("div","cx-cal-legend");legend.setAttribute("role","group");legend.setAttribute("aria-label",this.calLabels.calendarSources);for(const source of sources){const button=h("button","cx-cal-src"),swatch=h("i","cx-cal-src-swatch"),text=h("span");button.type="button";button.dataset.cxSource=source.id;button.setAttribute("aria-pressed",String(!this.calHiddenSources.has(source.id)));if(source.tone)button.style.setProperty("--cx-ev",toneVariable(source.tone,false,"var(--hfx-color-accent,light-dark(#11776f,#37c9b8))"));if(source.color)button.style.setProperty("--cx-ev",source.color);swatch.setAttribute("aria-hidden","true");setText(text,source.label);button.append(swatch,text);button.addEventListener("click",()=>this.calToggleSource(source.id));legend.appendChild(button)}
|
|
147
157
|
return legend},setCalendarSource(id,visible){return this.calToggleSource(id,visible)},calToggleSource(id,show){const hidden=this.calHiddenSources,source=this.cal.sources.find(item=>item.id===id);if(!source)throw new Error("CanopyX: the calendar has no source "+id+".");const visible=show===undefined?hidden.has(id):!!show;if(visible)hidden.delete(id);else hidden.add(id);this.calLegend?.querySelector('[data-cx-source="'+CSS.escape(id)+ '"]')?.setAttribute("aria-pressed",String(visible));this.calSourceVersion=(this.calSourceVersion||0)+ 1;if(this.calAllEvents){this.calEvents=this.calShownEvents(this.calAllEvents);this.calPaint({keep:true})}
|
|
148
|
-
this.emit("calendarSource",{id,visible});return visible},calShownEvents(events){const hidden=this.calHiddenSources;return hidden?.size?events.filter(event=>!event.base.source||!hidden.has(event.base.source)):events}});const
|
|
158
|
+
this.emit("calendarSource",{id,visible});if(source.url)this.calReload({keep:true});return visible},calShownEvents(events){const hidden=this.calHiddenSources;return hidden?.size?events.filter(event=>!event.base.source||!hidden.has(event.base.source)):events}});Object.assign(DataGrid.prototype,{async calLoadJson(from,to,signal){const rows=[],errors=[],cache=this.calJsonCache||=new Map(),budget={bytes:0,rows:0,limit:this.cal.limit};const sources=this.cal.sources.filter(source=>source.url&&!this.calHiddenSources.has(source.id));let next=0;const load=async source=>{const url=new URL(source.url,location.href);for(const[key,value]of Object.entries(source.fetchParams))url.searchParams.set(key,value);url.searchParams.set("start",calIso(from));url.searchParams.set("end",calIso(to));url.searchParams.set("timeZone",this.calZone.name);const key=source.id+":"+url.href;try{let entry=cache.get(key);if(!entry){if(url.origin!==location.origin||!["http:","https:"].includes(url.protocol)||url.username||url.password)throw new Error("JSON feeds need a same-origin http(s) page.");const response=await fetch(url.href,{signal,credentials:"same-origin",redirect:"error",headers:{Accept:"application/json"}});if(!response.ok)throw new Error("HTTP "+response.status);const body=await calJsonText(response,signal,budget);const items=calJsonRows(JSON.parse(body.text),source.id);calJsonCharge(budget,0,items.length);if(signal.aborted)throw new DOMException("Aborted","AbortError");entry={items,bytes:body.bytes};cache.set(key,entry);while(cache.size>12)cache.delete(cache.keys().next().value)}else calJsonCharge(budget,entry.bytes,entry.items.length);rows.push(...entry.items)}catch(error){if(signal.aborted||error.name==="AbortError")throw error;errors.push(source.label+": "+error.message);this.emit("error",{error,source:source.id})}};await Promise.all(Array.from({length:Math.min(4,sources.length)},async()=>{while(next<sources.length&&!budget.problem)await load(sources[next++])}));return{rows,errors}}});async function calJsonText(response,signal,budget){const max=8000000;if(Number(response.headers.get("content-length"))>max)throw new Error("JSON feed exceeds 8 MB.");if(!response.body?.getReader){const text=await response.text();if(text.length>2000000)throw new Error("JSON feed exceeds 2 million characters.");const bytes=new TextEncoder().encode(text).byteLength;calJsonCharge(budget,bytes,0);return{text,bytes}}
|
|
159
|
+
const reader=response.body.getReader(),decoder=new TextDecoder();let bytes=0,text="";try{for(;;){if(signal.aborted)throw new DOMException("Aborted","AbortError");const chunk=await reader.read();if(chunk.done)break;bytes +=chunk.value.byteLength;if(bytes>max)throw new Error("JSON feed exceeds 8 MB.");calJsonCharge(budget,chunk.value.byteLength,0);text +=decoder.decode(chunk.value,{stream:true});if(text.length>2000000)throw new Error("JSON feed exceeds 2 million characters.")}
|
|
160
|
+
return{text:text+decoder.decode(),bytes}}finally{await reader.cancel().catch(()=>{});reader.releaseLock()}}
|
|
161
|
+
function calJsonCharge(budget,bytes,rows){budget.problem||=budget.bytes+bytes>16000000?"JSON feeds exceed the combined 16 MB limit.":
|
|
162
|
+
budget.rows+rows>budget.limit?"JSON feeds exceed the combined event limit of "+budget.limit+".":null;if(budget.problem)throw new Error(budget.problem);budget.bytes +=bytes;budget.rows +=rows}
|
|
163
|
+
function calJsonRows(items,source){if(!Array.isArray(items)||items.length>5000)throw new Error("A JSON feed is an array of at most 5,000 events.");const ids=new Set(),identity=id=>"json:"+JSON.stringify([source,id]);return items.map((item,index)=>{if(!item||typeof item!=="object"||Array.isArray(item)||typeof item.title!=="string"||item.title.length>1000||
|
|
164
|
+
typeof item.start!=="string"||!Number.isFinite(calInstant(item.start.length===10?item.start+"T00:00:00Z":item.start))||
|
|
165
|
+
item.end!=null&&(typeof item.end!=="string"||!Number.isFinite(calInstant(item.end.length===10?item.end+"T00:00:00Z":item.end))))throw new Error("A JSON event needs a title and ISO start/end values.");const id=item.id??String(index+1);if(typeof id!=="string"||!CAL_ID.test(id)||ids.has(id))throw new Error("JSON event ids must be unique identifiers.");ids.add(id);for(const name of["description","resource","recurrence","exceptions","series","recurrenceId"])if(item[name]!=null&&(typeof item[name]!=="string"||item[name].length>10000))throw new Error("Invalid JSON event "+name+".");if(item.allDay!==undefined&&typeof item.allDay!=="boolean")throw new Error("JSON allDay is a boolean.");const allDay=item.allDay??item.start.length===10,normalize=value=>value?.length===10?value+"T00:00:00Z":value;return{id:identity(id),external:source,cells:{},fields:{title:item.title,start:normalize(item.start),end:normalize(item.end),allDay,description:item.description||"",resource:item.resource||null,resourceIds:calResourceIds(item.resourceIds),recurrence:item.recurrence,exceptions:item.exceptions,series:item.series?identity(item.series):null,recurrenceId:normalize(item.recurrenceId)}}})}
|
|
166
|
+
const CAL_LANE=22,CAL_NUMBER=26;const calDayOrder=(a,b)=>(b.allDay-a.allDay)||a.start-b.start||(a.id<b.id?-1:1);Object.assign(DataGrid.prototype,{calWeekdays(){const days=[];for(let i=0;i<7;i++){const dow=(this.cal.firstDay+i)%7;if(!this.cal.hidden.has(dow))days.push(dow)}
|
|
149
167
|
return days},calWeekdayRow(className,corner){const row=h("div",className),fmt=this.calFmt;row.setAttribute("role","row");if(corner){const cell=h("div","cx-cal-corner");cell.setAttribute("role","columnheader");setText(cell,corner===true?"":corner);row.appendChild(cell)}
|
|
150
168
|
for(const dow of this.calWeekdays()){const cell=h("div","cx-cal-dowc"),day=calDayNumber(2023,1,1)+ dow;cell.setAttribute("role","columnheader");cell.setAttribute("aria-label",fmt.weekday(day,"long"));setText(cell,fmt.weekday(day,this.calPhone?"narrow":"short"));row.appendChild(cell)}
|
|
151
169
|
return row},calDayName(day,count,ranges){const parts=[this.calFmt.longDay(day)];if(day===this.calZone.day(Date.now()))parts.push(this.calLabels.calendarToday);for(const range of ranges||[])parts.push(range.label);parts.push(count?calCount(this,this.calLabels.calendarEvents,count):this.calLabels.calendarNoEvents);return parts.join(", ")},calBackgroundsOn(day,resource,partial){const out=[];for(const range of this.calRanges)if(range.sd<=day&&range.ed>=day&&(resource===undefined?!range.inverse||range.resource==null:range.resource==null||range.resource===resource)&&
|
|
152
|
-
(partial||!range.inverse||range.start<=this.calZone.start(day)&&range.end>=this.calZone.start(day+1)))out.push(range);for(const event of this.calEvents)if(event.background&&event.sd<=day&&event.ed>=day&&(resource===undefined
|
|
170
|
+
(partial||!range.inverse||range.start<=this.calZone.start(day)&&range.end>=this.calZone.start(day+1)))out.push(range);for(const event of this.calEvents)if(event.background&&event.sd<=day&&event.ed>=day&&(resource===undefined||!this.calEventResources(event).length||this.calHasResource(event,resource)))
|
|
153
171
|
out.push({id:event.id,label:event.title,tone:event.tone||"info",kind:"background",sd:event.sd,ed:event.ed,start:event.start,end:event.end,resource:event.resource});return out},calPaintMonth(){const range=this.calRange,body=this.calBody,weekCount=Math.round((range.lastDay-range.firstDay+1)/ 7);
|
|
154
172
|
const available=body.clientHeight-CAL_NUMBER-4,rowHeight=this.calPhone?44:Math.max(96,Math.floor(available / weekCount));
|
|
155
173
|
this.calCells=new Map();const grid=this.calMonthGrid(range.firstDay,range.lastDay,range.month,{rowHeight,dots:this.calPhone,outside:this.cal.outsideDays,labelledBy:this.calTitle.id});body.replaceChildren(grid);this.calGrid=grid;if(this.calPhone){const day=this.calFocus?.day??this.calDate;body.appendChild(this.calDayList(day));this.calMarkListedDay(day)}},calMonthGrid(first,last,monthStart,options){const weekdays=this.calWeekdays(),today=this.calZone.day(Date.now()),month=calDayParts(monthStart).m,monthEnd=calAddMonths(monthStart,1)- 1;const events=this.calEvents.filter(event=>!event.background),rowHeight=options.rowHeight,valid=this.cal.validRange;const grid=h("div","cx-cal-month" +(options.className?" "+options.className:""));grid.setAttribute("role","grid");if(options.labelledBy)grid.setAttribute("aria-labelledby",options.labelledBy);grid.setAttribute("aria-readonly",String(!this.cal.editable&&!this.cal.create));grid.style.setProperty("--cx-cal-cols",String(weekdays.length));grid.appendChild(this.calWeekdayRow("cx-cal-dow"));const weeks=h("div","cx-cal-weeks");weeks.setAttribute("role","rowgroup");grid.appendChild(weeks);const fit=Math.max(1,Math.floor((rowHeight-CAL_NUMBER-4)/ CAL_LANE));
|
|
@@ -160,8 +178,8 @@ cell.style.gridColumn=String(order.get(day)+ 1);cells.push(cell);this.calCells.s
|
|
|
160
178
|
const weekEvents=events.filter(event=>event.sd<=week+6&&event.ed>=week);const placed=calLanes(weekEvents,week,week+6,day=>order.has(day)&&!pad(day)?order.get(day):-1);if(this.calPrinting)row.style.minHeight=Math.max(rowHeight,CAL_NUMBER+6 +(Math.max(-1,...placed.map(item=>item.lane))+ 1)*CAL_LANE)+ "px";const totals=new Array(shown.length).fill(0);for(const item of placed)for(let col=item.from;col<=item.to;col++)totals[col]++;const dayLimit=totals.map(total=>total>limit?Math.max(0,limit-1):limit),visible=new Array(shown.length).fill(0);for(const item of placed){let run=null;const flush=()=>{if(!run)return;const event=item.event,bar=calInAllDayRow(event)||event.ed>event.sd,element=this.calEventElement(event,bar?"bar":"chip");element.style.setProperty("--c",String(run.from));element.style.setProperty("--s",String(run.to-run.from+1));element.style.setProperty("--l",String(item.lane));if(event.sd<shown[run.from])element.classList.add("cx-cal-cont-start");if(event.ed>shown[run.to])element.classList.add("cx-cal-cont-end");cells[run.from].appendChild(element);run=null};for(let col=item.from;col<=item.to;col++){if(item.lane<dayLimit[col]){visible[col]++;if(run)run.to=col;else run={from:col,to:col}}
|
|
161
179
|
else flush()}
|
|
162
180
|
flush()}
|
|
163
|
-
cells.forEach((cell,col)=>{if(cell.classList.contains("cx-cal-pad"))return;const day=shown[col],backgrounds=this.calBackgroundsOn(day);if(backgrounds.length){cell.classList.add("cx-cal-bgday");cell.style.setProperty("--cx-bg-tone",
|
|
164
|
-
const hidden=totals[col]- visible[col];cell.setAttribute("aria-label",this.calDayName(day,totals[col],backgrounds));if(options.dots&&totals[col]){const dots=h("span","cx-cal-dots");dots.setAttribute("aria-hidden","true");for(const item of placed.filter(entry=>entry.from<=col&&entry.to>=col).slice(0,3)){const dot=h("i");if(item.event.tone)dot.style.setProperty("--cx-ev",
|
|
181
|
+
cells.forEach((cell,col)=>{if(cell.classList.contains("cx-cal-pad"))return;const day=shown[col],backgrounds=this.calBackgroundsOn(day);if(backgrounds.length){cell.classList.add("cx-cal-bgday");cell.style.setProperty("--cx-bg-tone",toneVariable(backgrounds[0].tone,false,"var(--hfx-color-status-info-fg,light-dark(#2b5fc0,#74a2f7))"));if(backgrounds[0].sd===day||col===0){const label=h("span","cx-cal-bglabel");setText(label,backgrounds[0].label);label.setAttribute("aria-hidden","true");cell.appendChild(label)}}
|
|
182
|
+
const hidden=totals[col]- visible[col];cell.setAttribute("aria-label",this.calDayName(day,totals[col],backgrounds));if(options.dots&&totals[col]){const dots=h("span","cx-cal-dots");dots.setAttribute("aria-hidden","true");for(const item of placed.filter(entry=>entry.from<=col&&entry.to>=col).slice(0,3)){const dot=h("i");if(item.event.tone)dot.style.setProperty("--cx-ev",toneVariable(item.event.tone,false,"var(--hfx-color-accent,light-dark(#11776f,#37c9b8))"));dots.appendChild(dot)}
|
|
165
183
|
cell.appendChild(dots)}else if(hidden>0){const more=gridButton(fill(this.calLabels.calendarMore,{count:numberFormat(this.lang).format(hidden)}),()=>{});more.className="cx-cal-more-btn";more.tabIndex=-1;more.dataset.cxDay=String(day);more.setAttribute("aria-label",fill(this.calLabels.calendarMoreLabel,{count:numberFormat(this.lang).format(hidden),date:this.calFmt.longDay(day)}));more.style.setProperty("--l",String(dayLimit[col]));more.style.setProperty("--c",String(col));cell.appendChild(more)}});weeks.appendChild(row)}
|
|
166
184
|
return grid},calWeekNumber(day){if(this.cal.weekNumbers!=="locale"){const thursday=day -(calDow(day)+ 6)%7+3,year=calDayParts(thursday).y;return Math.floor((thursday-calDayNumber(year,1,1))/ 7) + 1;
|
|
167
185
|
}
|
|
@@ -182,38 +200,40 @@ this.calRestoreFocus(true)}});const CAL_DAY_GRIDS=["month","mini","months","week
|
|
|
182
200
|
Object.assign(DataGrid.prototype,{calPaintMini(){const range=this.calRange;this.calCells=new Map();const grid=this.calMonthGrid(range.firstDay,range.lastDay,range.month,{rowHeight:34,dots:true,outside:this.cal.outsideDays,labelledBy:this.calTitle.id,className:"cx-cal-mini"});this.calBody.replaceChildren(grid);this.calGrid=grid},calPaintMonths(){const range=this.calRange,stack=h("div","cx-cal-months");this.calCells=new Map();for(let month=range.month;month<=range.lastDay;month=calAddMonths(month,1)){const section=h("section","cx-cal-mblock"),title=h("h3","cx-cal-mtitle"),last=calAddMonths(month,1)- 1;title.id=this.id+"-cal-m"+month;setText(title,this.calFmt.monthTitle(month));const first=calWeekStart(month,this.cal.firstDay),end=calWeekStart(last,this.cal.firstDay)+ 6;section.append(title,this.calMonthGrid(first,end,month,{rowHeight:92,dots:this.calPhone,outside:false,labelledBy:title.id}));stack.appendChild(section)}
|
|
183
201
|
this.calBody.replaceChildren(stack);this.calGrid=stack},calPaintList(){const range=this.calRange,days=this.calShownDays(range.firstDay,range.lastDay),today=this.calZone.day(Date.now()),valid=this.cal.validRange,fmt=this.calFmt;const grid=h("div","cx-cal-list" +(days.length===1?" cx-cal-list-day":"")),row=h("div","cx-cal-lrow");grid.setAttribute("role","grid");grid.setAttribute("aria-labelledby",this.calTitle.id);grid.setAttribute("aria-readonly",String(!this.cal.editable&&!this.cal.create));row.setAttribute("role","row");grid.style.setProperty("--cx-cal-cols",String(days.length));this.calCells=new Map();for(const day of days){const events=this.calEventsOn(day),invalid=valid&&(day<valid.first||day>valid.last);const cell=h("div","cx-cal-day cx-cal-lday" +(day===today?" cx-cal-today":"")+(invalid?" cx-cal-invalid":""));cell.setAttribute("role","gridcell");cell.tabIndex=-1;cell.dataset.cxDay=String(day);if(invalid)cell.setAttribute("aria-disabled","true");if(day===today)cell.setAttribute("aria-current","date");cell.setAttribute("aria-label",this.calDayName(day,events.length,this.calBackgroundsOn(day)));const head=h("div","cx-cal-lhead"),name=h("span","cx-cal-dayname"),number=h("span","cx-cal-num");head.setAttribute("aria-hidden","true");setText(name,fmt.weekday(day,days.length===1?"long":"short"));setText(number,fmt.dayNumber(day));head.append(name,number);if(this.cal.weekNumbers&&day===days[0]){const wk=h("span","cx-cal-wknum");setText(wk,fill(this.calLabels.calendarWeekShort,{number:this.calWeekNumber(day)}));head.appendChild(wk)}
|
|
184
202
|
cell.appendChild(head);if(!events.length){const empty=h("p","cx-cal-empty");setText(empty,this.calLabels.calendarNoEvents);empty.setAttribute("aria-hidden","true");cell.appendChild(empty)}
|
|
185
|
-
for(const event of events)cell.appendChild(this.calEventElement(event,"item"));const backgrounds=this.calBackgroundsOn(day);if(backgrounds.length){cell.classList.add("cx-cal-bgday");cell.style.setProperty("--cx-bg-tone",
|
|
203
|
+
for(const event of events)cell.appendChild(this.calEventElement(event,"item"));const backgrounds=this.calBackgroundsOn(day);if(backgrounds.length){cell.classList.add("cx-cal-bgday");cell.style.setProperty("--cx-bg-tone",toneVariable(backgrounds[0].tone,false,"var(--hfx-color-status-info-fg,light-dark(#2b5fc0,#74a2f7))"))}
|
|
186
204
|
this.calCells.set(day,cell);row.appendChild(cell)}
|
|
187
|
-
grid.appendChild(row);this.calBody.replaceChildren(grid);this.calGrid=grid}});const CAL_SLOT_PX=24,CAL_GUTTER=64,CAL_ALLDAY_LANES=3;Object.assign(DataGrid.prototype,{calTimeColumns(days){const all=this.calResourceColumns?.()||null;this.calResourcePages=null;if(!all)return days.map(day=>({day,resource:null}))
|
|
205
|
+
grid.appendChild(row);this.calBody.replaceChildren(grid);this.calGrid=grid}});const CAL_SLOT_PX=24,CAL_GUTTER=64,CAL_ALLDAY_LANES=3;Object.assign(DataGrid.prototype,{calTimeColumns(days){const all=this.calResourceColumns?.()||null;this.calResourcePages=null;if(!all){this.calTimeWindow=null;return days.map(day=>({day,resource:null}))}
|
|
206
|
+
const size=this.calPhone&&!this.calPrinting&&all.length>2?(days.length===1?2:1):all.length,pages=Math.ceil(all.length / size);
|
|
188
207
|
const page=Math.min(Math.floor((this.calResourceFirst||0)/ size), pages - 1);
|
|
189
|
-
if(pages>1)this.calResourcePages={all,size,pages,page};const resources=all.slice(page*size,page*size+size);const out=[];for(const day of days)for(const resource of resources)out.push({day,resource:resource.id,label:resource.label,group:resource.group});return out},calResourcePicker(){const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
208
|
+
if(pages>1)this.calResourcePages={all,size,pages,page};const resources=all.slice(page*size,page*size+size);const out=[];for(const day of days)for(const resource of resources)out.push({day,resource:resource.id,label:resource.label,group:resource.group});return this.calWindowTimeColumns(out)},calResourcePicker(){const window=this.calTimeWindow,paging=this.calResourcePages;const total=window?.total??paging.all.length,size=window?.size??paging.size,first=window?.first??paging.page*size;const bar=h("div","cx-cal-rpick"),select=h("select","cx-cal-rpick-select"),maximum=window?total-size:Math.floor((total-1)/ size) * size;
|
|
209
|
+
const multiDay=window&&window.columns[0].day!==window.columns.at(-1).day;const caption=column=>this.calFmt.day({weekday:"short",month:"short",day:"numeric"},column.day)+ " "+column.label;const range=at=>multiDay?caption(window.columns[at])+ " – "+caption(window.columns[Math.min(total-1,at+size-1)]):
|
|
210
|
+
fill(this.calLabels.calendarResourceWindow,{resources:this.calLabels.calendarResources,start:this.calFmt.number(at+1),end:this.calFmt.number(Math.min(total,at+size)),total:this.calFmt.number(total)});const choices=new Set([first,maximum]);for(let at=0;at<total;at +=size)choices.add(Math.min(at,maximum));select.setAttribute("aria-label",range(first));for(const at of[...choices].sort((a,b)=>a-b)){const option=h("option");option.value=String(at);setText(option,range(at));select.appendChild(option)}
|
|
211
|
+
const show=(at,focus)=>{if(window)this.calTimeFirst=at;else this.calResourceFirst=at;this.calPaint({keep:true});this.calBody.querySelector(focus)?.focus({preventScroll:true})};select.value=String(first);select.addEventListener("change",()=>show(Number(select.value),".cx-cal-rpick-select"));const buttons=[-1,1].map(sign=>{const button=h("button","cx-btn ghost cx-cal-rpick-" +(sign<0?"prev":"next")),at=Math.max(0,Math.min(maximum,first+sign*size));button.type="button";button.disabled=at===first;setText(button,sign<0?"←":"→");button.setAttribute("aria-label",(sign<0?this.calLabels.calendarPrevious:this.calLabels.calendarNext)+ ": "+range(at));button.addEventListener("click",()=>show(at,".cx-cal-rpick-select"));return button});bar.append(buttons[0],select,buttons[1]);return bar},calPaintTime(){const cal=this.cal,range=this.calRange,days=this.calShownDays(range.firstDay,range.lastDay),columns=this.calTimeColumns(days);const today=this.calZone.day(Date.now()),slot=cal.slotMinutes,ppm=CAL_SLOT_PX / slot, labels = this.calLabels, fmt = this.calFmt;
|
|
212
|
+
const grid=h("div","cx-cal-time");grid.setAttribute("role","grid");grid.setAttribute("aria-labelledby",this.calTitle.id);grid.setAttribute("aria-readonly",String(!cal.editable));grid.style.setProperty("--cx-cal-n",String(columns.length));grid.style.setProperty("--cx-cal-ppm",ppm+"px");grid.style.setProperty("--cx-cal-gutter",CAL_GUTTER+"px");const head=h("div","cx-cal-thead");head.setAttribute("role","rowgroup");const dayRow=h("div","cx-cal-trow cx-cal-heads");dayRow.setAttribute("role","row");const corner=h("div","cx-cal-corner");corner.setAttribute("role","columnheader");dayRow.appendChild(corner);const resourceColumns=columns.some(column=>column.resource!=null),perDay=resourceColumns?2:1;const dayRuns=[];for(const column of columns){const last=dayRuns.at(-1);if(last?.day===column.day)last.count++;else dayRuns.push({day:column.day,count:1,index:column.index??columns.indexOf(column)})}
|
|
213
|
+
for(const{day,count,index}of dayRuns){const cell=h("div","cx-cal-dayhead" +(day===today?" cx-cal-today":""));cell.setAttribute("role","columnheader");cell.dataset.cxDay=String(day);cell.setAttribute("aria-colindex",String(index+2));cell.style.flexGrow=String(count);cell.setAttribute("aria-label",fmt.longDay(day));if(count>1)cell.setAttribute("aria-colspan",String(count));if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
|
|
214
|
+
const name=h("span","cx-cal-dayname"),number=h("span","cx-cal-daynum");setText(name,fmt.weekday(day,"short"));setText(number,fmt.dayNumber(day));cell.append(name,number);if(day===today)cell.setAttribute("aria-current","date");dayRow.appendChild(cell)}
|
|
215
|
+
head.appendChild(dayRow);if(perDay>1&&columns.some(column=>column.group)){const groupRow=h("div","cx-cal-trow cx-cal-gheads");groupRow.setAttribute("role","row");const blank=h("div","cx-cal-corner");blank.setAttribute("role","columnheader");groupRow.appendChild(blank);for(let at=0;at<columns.length;){let end=at;while(end+1<columns.length&&columns[end+1].group===columns[at].group&&columns[end+1].day===columns[at].day)end++;const cell=h("div","cx-cal-ghead");cell.setAttribute("role","columnheader");cell.setAttribute("aria-colspan",String(end-at+1));cell.setAttribute("aria-colindex",String((columns[at].index??at)+ 2));cell.style.flexGrow=String(end-at+1);setText(cell,columns[at].group||"");cell.title=columns[at].group||"";groupRow.appendChild(cell);at=end+1}
|
|
195
216
|
head.appendChild(groupRow)}
|
|
196
|
-
if(perDay>1){const resourceRow=h("div","cx-cal-trow cx-cal-rheads");resourceRow.setAttribute("role","row");const blank=h("div","cx-cal-corner");blank.setAttribute("role","columnheader");resourceRow.appendChild(blank);for(const column of columns){const cell=h("div","cx-cal-rhead");cell.setAttribute("role","columnheader");setText(cell,column.label);cell.title=column.label;resourceRow.appendChild(cell)}
|
|
217
|
+
if(perDay>1){const resourceRow=h("div","cx-cal-trow cx-cal-rheads");resourceRow.setAttribute("role","row");const blank=h("div","cx-cal-corner");blank.setAttribute("role","columnheader");resourceRow.appendChild(blank);for(const column of columns){const cell=h("div","cx-cal-rhead");cell.setAttribute("role","columnheader");cell.setAttribute("aria-colindex",String((column.index??columns.indexOf(column))+ 2));setText(cell,column.label);cell.title=column.label;resourceRow.appendChild(cell)}
|
|
197
218
|
head.appendChild(resourceRow)}
|
|
198
|
-
head.appendChild(this.calAllDayRow(columns,days));grid.appendChild(head);const scroller=this.calScroller=h("div","cx-cal-scroll"),slots=h("div","cx-cal-slots");slots.setAttribute("role","rowgroup");const visible=cal.maxMinutes-cal.minMinutes;slots.style.height=visible*ppm+"px";this.calSlotCells=new Map();const segmentsByColumn=columns.map(column=>calSegments(this,this.calEvents.filter(event=>column.resource==null||event.resource
|
|
199
|
-
row.appendChild(label);columns.forEach((column,index)=>{const cell=h("div","cx-cal-slot");cell.setAttribute("role","gridcell");cell.tabIndex=-1;cell.dataset.cxDay=String(column.day);cell.dataset.cxMinutes=String(minutes);if(column.resource!=null)cell.dataset.cxResource=column.resource;if(!this.calAllowsSpan(column.day*CAL_DAY,(column.day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
|
|
219
|
+
head.appendChild(this.calAllDayRow(columns,days));grid.appendChild(head);const scroller=this.calScroller=h("div","cx-cal-scroll"),slots=h("div","cx-cal-slots");slots.setAttribute("role","rowgroup");const visible=cal.maxMinutes-cal.minMinutes;slots.style.height=visible*ppm+"px";scroller.style.setProperty("--cx-cal-slots-height",visible*ppm+"px");this.calSlotCells=new Map();const segmentsByColumn=columns.map(column=>calSegments(this,this.calEvents.filter(event=>column.resource==null||this.calHasResource(event,column.resource)).map(event=>column.resource==null?event:{...event,resource:column.resource}),column.day,cal.minMinutes,cal.maxMinutes));for(let minutes=cal.minMinutes;minutes<cal.maxMinutes;minutes +=slot){const row=h("div","cx-cal-srow" +(minutes%60===0?" cx-cal-hour":""));row.setAttribute("role","row");const label=h("div","cx-cal-stime");label.setAttribute("role","rowheader");if(minutes%60===0||slot>=60)setText(label,fmt.clock(minutes));else{label.setAttribute("aria-label",fmt.clock(minutes))}
|
|
220
|
+
row.appendChild(label);columns.forEach((column,index)=>{const cell=h("div","cx-cal-slot");cell.setAttribute("role","gridcell");cell.tabIndex=-1;cell.setAttribute("aria-colindex",String((column.index??index)+ 2));cell.dataset.cxDay=String(column.day);cell.dataset.cxMinutes=String(minutes);if(column.resource!=null)cell.dataset.cxResource=column.resource;if(!this.calAllowsSpan(column.day*CAL_DAY,(column.day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
|
|
200
221
|
cell.setAttribute("aria-label",fmt.longDay(column.day)+ ", "+fmt.clock(minutes)+(column.label?", "+column.label:""));if(column.day===today)cell.classList.add("cx-cal-today");this.calSlotCells.set(index+":"+minutes,cell);row.appendChild(cell)});slots.appendChild(row)}
|
|
201
222
|
columns.forEach((column,index)=>{this.calShade(slots,column,index,ppm);for(const segment of segmentsByColumn[index]){const event=segment.event,element=this.calEventElement(event,"block");element.style.setProperty("--d",String(index));element.style.setProperty("--c",String(segment.col));element.style.setProperty("--k",String(segment.cols));element.style.setProperty("--sp",String(segment.span));element.style.setProperty("--t",String(segment.top-cal.minMinutes));element.style.setProperty("--h",String(Math.max(segment.bottom-segment.top,event.marker?0:this.calMinSegment)));if(segment.clippedTop)element.classList.add("cx-cal-cont-start");if(segment.clippedBottom)element.classList.add("cx-cal-cont-end");if((segment.bottom-segment.top)*ppm<40)element.classList.add("cx-cal-short");const start=Math.min(cal.maxMinutes-slot,Math.floor(segment.top / slot) * slot);
|
|
202
223
|
(this.calSlotCells.get(index+":"+start)||slots).appendChild(element)}
|
|
203
224
|
const count=segmentsByColumn[index].length;if(count)for(let minutes=cal.minMinutes;minutes<cal.maxMinutes;minutes +=slot){const cell=this.calSlotCells.get(index+":"+minutes),starting=cell.querySelectorAll(".cx-cal-ev").length;if(starting)cell.setAttribute("aria-label",cell.getAttribute("aria-label")+ ", "+calCount(this,labels.calendarEvents,starting))}});if(cal.now){this.calNowLine=h("div","cx-cal-now");this.calNowLine.setAttribute("aria-hidden","true");slots.appendChild(this.calNowLine);this.calNowColumns=columns}
|
|
204
|
-
scroller.appendChild(slots);grid.appendChild(scroller);this.calBody.replaceChildren(...(this.calResourcePages?[this.calResourcePicker()]:[]),grid);this.calGrid=grid;if(cal.now)this.calTick();head.style.paddingInlineEnd=Math.max(0,scroller.offsetWidth-scroller.clientWidth)+ "px";if(!this.calScrolled||this.calScrolledView!==this.calView){this.calScrolled=true;this.calScrolledView=this.calView;const now=this.calZone.parts(Date.now()).minutes,shown=scroller.clientHeight / ppm;
|
|
205
|
-
const target=days.includes(today)&&now>cal.scrollMinutes+shown-60?now-60:cal.scrollMinutes;scroller.scrollTop=Math.max(0,(target-cal.minMinutes)*ppm-10)}},calAllDayRow(columns,days){const row=h("div","cx-cal-trow cx-cal-allrow");row.setAttribute("role","row");const label=h("div","cx-cal-stime cx-cal-alllabel");label.setAttribute("role","rowheader");setText(label,this.calLabels.calendarAllDay);row.appendChild(label);const index=new Map(),events=this.calEvents.filter(event=>!event.background&&calInAllDayRow(event));const perDay=columns.
|
|
206
|
-
|
|
207
|
-
if(column.resource!=null)cell.dataset.cxResource=column.resource;cells.push(cell);row.appendChild(cell);if(perDay===1)index.set(column.day,at)});const placed=[];if(perDay===1)placed.push(...calLanes(events,days[0],days[days.length-1],day=>index.has(day)?index.get(day):-1));else columns.forEach((column,at)=>{for(const event of events)if(event.sd<=column.day&&event.ed>=column.day&&event.resource===column.resource)placed.push({event,lane:0,from:at,to:at})});if(perDay>1){const lanes=new Map();for(const item of placed){item.lane=lanes.get(item.from)||0;lanes.set(item.from,item.lane+1)}}
|
|
225
|
+
scroller.appendChild(slots);grid.appendChild(scroller);this.calBody.replaceChildren(...(this.calResourcePages||this.calTimeWindow?[this.calResourcePicker()]:[]),grid);grid.style.maxHeight=(visible*ppm+head.offsetHeight+2)+ "px";if(this.calTimeWindow)grid.setAttribute("aria-colcount",String(this.calTimeWindow.total+1));this.calGrid=grid;if(cal.now)this.calTick();head.style.paddingInlineEnd=Math.max(0,scroller.offsetWidth-scroller.clientWidth)+ "px";if(!this.calScrolled||this.calScrolledView!==this.calView){this.calScrolled=true;this.calScrolledView=this.calView;const now=this.calZone.parts(Date.now()).minutes,shown=scroller.clientHeight / ppm;
|
|
226
|
+
const target=days.includes(today)&&now>cal.scrollMinutes+shown-60?now-60:cal.scrollMinutes;scroller.scrollTop=Math.max(0,(target-cal.minMinutes)*ppm-10)}},calAllDayRow(columns,days){const row=h("div","cx-cal-trow cx-cal-allrow");row.setAttribute("role","row");const label=h("div","cx-cal-stime cx-cal-alllabel");label.setAttribute("role","rowheader");setText(label,this.calLabels.calendarAllDay);row.appendChild(label);const index=new Map(),events=this.calEvents.filter(event=>!event.background&&calInAllDayRow(event));const perDay=columns.some(column=>column.resource!=null)?2:1,cells=[];columns.forEach((column,at)=>{const cell=h("div","cx-cal-allcell");cell.setAttribute("role","gridcell");cell.tabIndex=-1;cell.dataset.cxDay=String(column.day);cell.setAttribute("aria-colindex",String((column.index??at)+ 2));if(!this.calAllowsSpan(column.day*CAL_DAY,(column.day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
|
|
227
|
+
if(column.resource!=null)cell.dataset.cxResource=column.resource;cells.push(cell);row.appendChild(cell);if(perDay===1)index.set(column.day,at)});const placed=[];if(perDay===1)placed.push(...calLanes(events,days[0],days[days.length-1],day=>index.has(day)?index.get(day):-1));else columns.forEach((column,at)=>{for(const event of events)if(event.sd<=column.day&&event.ed>=column.day&&this.calHasResource(event,column.resource))placed.push({event:{...event,resource:column.resource},lane:0,from:at,to:at})});if(perDay>1){const lanes=new Map();for(const item of placed){item.lane=lanes.get(item.from)||0;lanes.set(item.from,item.lane+1)}}
|
|
208
228
|
const totals=new Array(columns.length).fill(0),shown=new Array(columns.length).fill(0);for(const item of placed)for(let at=item.from;at<=item.to;at++)totals[at]++;const lanes=Math.min(CAL_ALLDAY_LANES,Math.max(0,...placed.map(item=>item.lane+1))),limit=totals.map(total=>total>lanes?lanes-1:lanes);for(const item of placed){let from=null;const flush=to=>{if(from===null)return;const element=this.calEventElement(item.event,"bar");element.style.setProperty("--c",String(from));element.style.setProperty("--s",String(to-from+1));element.style.setProperty("--l",String(item.lane));if(item.event.sd<columns[from].day)element.classList.add("cx-cal-cont-start");if(item.event.ed>columns[to].day)element.classList.add("cx-cal-cont-end");cells[from].appendChild(element);from=null};for(let at=item.from;at<=item.to;at++){if(item.lane<limit[at]){shown[at]++;if(from===null)from=at}else flush(at-1)}
|
|
209
229
|
flush(item.to)}
|
|
210
|
-
cells.forEach((cell,at)=>{const hidden=totals[at]- shown[at],day=columns[at].day;cell.setAttribute("aria-label",this.calLabels.calendarAllDay+", "+this.calDayName(day,totals[at],this.calBackgroundsOn(day,columns[at].resource??undefined)));if(hidden>0){const more=gridButton(fill(this.calLabels.calendarMore,{count:numberFormat(this.lang).format(hidden)}),()=>{});more.className="cx-cal-more-btn";more.tabIndex=-1;more.dataset.cxDay=String(day);more.style.setProperty("--l",String(limit[at]));more.style.setProperty("--c",String(at));more.setAttribute("aria-label",fill(this.calLabels.calendarMoreLabel,{count:numberFormat(this.lang).format(hidden),date:this.calFmt.longDay(day)}));cell.appendChild(more)}});row.style.setProperty("--cx-cal-lanes",String(Math.max(1,lanes)));return row},calShade(slots,column,index,ppm){const cal=this.cal,dow=calDow(column.day),add=(from,to,cls,label,tone)=>{const top=Math.max(from,cal.minMinutes),bottom=Math.min(to,cal.maxMinutes);if(bottom<=top)return;const block=h("div","cx-cal-shade "+cls);block.setAttribute("aria-hidden","true");block.style.setProperty("--d",String(index));block.style.setProperty("--t",String(top-cal.minMinutes));block.style.setProperty("--h",String(bottom-top));if(tone)block.style.setProperty("--cx-bg-tone",
|
|
230
|
+
cells.forEach((cell,at)=>{const hidden=totals[at]- shown[at],day=columns[at].day;cell.setAttribute("aria-label",this.calLabels.calendarAllDay+", "+this.calDayName(day,totals[at],this.calBackgroundsOn(day,columns[at].resource??undefined)));if(hidden>0){const more=gridButton(fill(this.calLabels.calendarMore,{count:numberFormat(this.lang).format(hidden)}),()=>{});more.className="cx-cal-more-btn";more.tabIndex=-1;more.dataset.cxDay=String(day);more.style.setProperty("--l",String(limit[at]));more.style.setProperty("--c",String(at));more.setAttribute("aria-label",fill(this.calLabels.calendarMoreLabel,{count:numberFormat(this.lang).format(hidden),date:this.calFmt.longDay(day)}));cell.appendChild(more)}});row.style.setProperty("--cx-cal-lanes",String(Math.max(1,lanes)));return row},calShade(slots,column,index,ppm){const cal=this.cal,dow=calDow(column.day),add=(from,to,cls,label,tone)=>{const top=Math.max(from,cal.minMinutes),bottom=Math.min(to,cal.maxMinutes);if(bottom<=top)return;const block=h("div","cx-cal-shade "+cls);block.setAttribute("aria-hidden","true");block.style.setProperty("--d",String(index));block.style.setProperty("--t",String(top-cal.minMinutes));block.style.setProperty("--h",String(bottom-top));if(tone)block.style.setProperty("--cx-bg-tone",toneVariable(tone,false,"var(--hfx-color-status-info-fg,light-dark(#2b5fc0,#74a2f7))"));if(label&&(bottom-top)*ppm>=18){const text=h("span");setText(text,label);block.appendChild(text)}
|
|
211
231
|
slots.appendChild(block)};const hours=this.calHoursFor?.(column.resource,dow)??(cal.working&&cal.working.days.has(dow)?[cal.working]:cal.working?[]:null);if(hours){let at=0;for(const span of hours.slice().sort((a,b)=>a.start-b.start)){add(at,span.start,"cx-cal-off");at=Math.max(at,span.end)}
|
|
212
232
|
add(at,1440,"cx-cal-off")}
|
|
213
|
-
const zone=this.calZone,dayStart=zone.start(column.day),dayEnd=zone.start(column.day+1);for(const range of this.calBackgroundsOn(column.day,column.resource??undefined,true)){const from=range.start<=dayStart?0:zone.parts(range.start).minutes,to=range.end>=dayEnd?1440:zone.parts(range.end).minutes;add(from,to,range.kind==="unavailable"?"cx-cal-unavailable":"cx-cal-bgrange",range.label,range.kind==="unavailable"?null:range.tone)}}});Object.assign(DataGrid.prototype,{calKeyTime(event,target,eventElement){const key=event.key,cal=this.cal,slot=cal.slotMinutes;const cell=eventElement?this.calSlotOf(eventElement):target.closest(".cx-cal-slot,.cx-cal-allcell");if(!cell)return;const columns=[...this.calGrid.querySelectorAll(".cx-cal-allcell")],resource=cell.dataset.cxResource;const column=columns.findIndex(item=>item.dataset.cxDay===cell.dataset.cxDay&&item.dataset.cxResource===resource);const minutes=cell.dataset.cxMinutes===undefined?null:Number(cell.dataset.cxMinutes);const go=(index,at)=>{const fresh=[...this.calGrid.querySelectorAll(".cx-cal-allcell")],target=fresh[index==="last"?fresh.length-1:Math.max(0,Math.min(fresh.length-1,index))];if(!target)return;const day=Number(target.dataset.cxDay);if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true))return;this.calFocus={day,minutes:at,event:null,resource:target.dataset.cxResource};const element=at===null?target:this.calGrid.querySelector('.cx-cal-slot[data-cx-day="'+day+'"][data-cx-minutes="'+at+'"]' +(target.dataset.cxResource!==undefined?'[data-cx-resource="'+CSS.escape(target.dataset.cxResource)+ '"]':""));if(element){
|
|
233
|
+
const zone=this.calZone,dayStart=zone.start(column.day),dayEnd=zone.start(column.day+1);for(const range of this.calBackgroundsOn(column.day,column.resource??undefined,true)){const from=range.start<=dayStart?0:zone.parts(range.start).minutes,to=range.end>=dayEnd?1440:zone.parts(range.end).minutes;add(from,to,range.kind==="unavailable"?"cx-cal-unavailable":"cx-cal-bgrange",range.label,range.kind==="unavailable"?null:range.tone)}}});Object.assign(DataGrid.prototype,{calKeyTime(event,target,eventElement){const key=event.key,cal=this.cal,slot=cal.slotMinutes;const cell=eventElement?this.calSlotOf(eventElement):target.closest(".cx-cal-slot,.cx-cal-allcell");if(!cell)return;const columns=[...this.calGrid.querySelectorAll(".cx-cal-allcell")],resource=cell.dataset.cxResource;const column=columns.findIndex(item=>item.dataset.cxDay===cell.dataset.cxDay&&item.dataset.cxResource===resource);const minutes=cell.dataset.cxMinutes===undefined?null:Number(cell.dataset.cxMinutes);const go=(index,at)=>{const fresh=[...this.calGrid.querySelectorAll(".cx-cal-allcell")],target=fresh[index==="last"?fresh.length-1:Math.max(0,Math.min(fresh.length-1,index))];if(!target)return;const day=Number(target.dataset.cxDay);if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true))return;this.calFocus={day,minutes:at,event:null,resource:target.dataset.cxResource};const element=at===null?target:this.calGrid.querySelector('.cx-cal-slot[data-cx-day="'+day+'"][data-cx-minutes="'+at+'"]' +(target.dataset.cxResource!==undefined?'[data-cx-resource="'+CSS.escape(target.dataset.cxResource)+ '"]':""));if(element){element.tabIndex=0;element.focus()}};if(eventElement){if(key==="Escape"||key==="ArrowLeft"||key==="ArrowRight"){event.preventDefault();go(column +(key==="ArrowLeft"?-1:key==="ArrowRight"?1:0),minutes);return}
|
|
214
234
|
if(key==="ArrowUp"||key==="ArrowDown"){event.preventDefault();const list=[...this.calGrid.querySelectorAll(".cx-cal-ev")].filter(item=>{const owner=this.calSlotOf(item);return owner&&owner.dataset.cxDay===cell.dataset.cxDay&&owner.dataset.cxResource===resource});const next=list[list.indexOf(eventElement)+(key==="ArrowDown"?1:-1)];if(next){eventElement.tabIndex=-1;next.tabIndex=0;next.focus()}}
|
|
215
235
|
return}
|
|
216
|
-
const first=cal.minMinutes,last=cal.maxMinutes-slot;if(key==="ArrowLeft"||key==="ArrowRight"){event.preventDefault();const next=column +(key==="ArrowLeft"?-1:1),pages=this.calResourcePages;if(pages&&(next<0?pages.page>0:next>=columns.length&&pages.page<pages.pages-1)){this.calResourceFirst=(pages.page +(next<0?-1:1))*pages.size;this.calPaint({keep:true});go(next<0?"last":0,minutes);return}
|
|
236
|
+
const first=cal.minMinutes,last=cal.maxMinutes-slot;if(key==="ArrowLeft"||key==="ArrowRight"){event.preventDefault();const next=column +(key==="ArrowLeft"?-1:1),pages=this.calResourcePages;if(this.calTimeWindow&&this.calTimeReveal(this.calTimeWindow.first+next,minutes))return;if(pages&&(next<0?pages.page>0:next>=columns.length&&pages.page<pages.pages-1)){this.calResourceFirst=(pages.page +(next<0?-1:1))*pages.size;this.calPaint({keep:true});go(next<0?"last":0,minutes);return}
|
|
217
237
|
if(next<0||next>=columns.length){this.calendarStep(next<0?-1:1).then(()=>go(next<0?"last":0,minutes));return}
|
|
218
238
|
go(next,minutes)}else if(key==="ArrowUp"||key==="ArrowDown"){event.preventDefault();const next=minutes===null?(key==="ArrowDown"?first:null):minutes +(key==="ArrowUp"?-slot:slot);if(minutes!==null&&next<first){go(column,null);return}
|
|
219
239
|
if(next===null||next>last)return;if(event.shiftKey&&minutes!==null){const anchor=this.calAnchorMinutes??minutes;this.calAnchorMinutes=anchor;this.calSelectSlots(Number(cell.dataset.cxDay),Math.min(anchor,next),Math.max(anchor,next)+ slot,resource)}else this.calAnchorMinutes=null;go(column,next)}else if(key==="Home"||key==="End"){event.preventDefault();go(column,key==="Home"?first:last)}
|
|
@@ -240,14 +260,14 @@ legend.appendChild(high);this.calBody.replaceChildren(year,legend);this.calGrid=
|
|
|
240
260
|
if(events.length>shown.length){const item=h("li","cx-cal-yl-more"),more=gridButton(fill(this.calLabels.calendarMore,{count:numberFormat(this.lang).format(events.length-shown.length)}),()=>{if(this.cal.views.includes("month"))this.calGo(first,"month",true);else if(this.cal.views.includes("agenda"))this.calGo(first,"agenda",true)});more.className="cx-cal-yl-morebtn";item.appendChild(more);list.appendChild(item)}
|
|
241
261
|
return list},calKeyYear(event,cell){const day=Number(cell.dataset.cxDay),key=event.key,range=this.calRange;if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true))return;let next=null;if(key==="ArrowLeft"||key==="ArrowRight")next=day +(key==="ArrowLeft"?-1:1);else if(key==="ArrowUp"||key==="ArrowDown")next=day +(key==="ArrowUp"?-7:7);else if(key==="Home")next=calMonthStart(day);else if(key==="End")next=calAddMonths(calMonthStart(day),1)- 1;else if(key==="PageUp"||key==="PageDown")next=calAddMonths(day,key==="PageUp"?-12:12);else if(key==="Enter"){event.preventDefault();if(this.cal.views.includes("day"))this.calGo(day,"day",true).then(()=>this.calRestoreFocus(true));else if(this.cal.views.includes("week"))this.calGo(day,"week",true).then(()=>this.calRestoreFocus(true));return}
|
|
242
262
|
if(next===null)return;event.preventDefault();next=this.calClampDay(next);if(next<range.firstDay||next>range.lastDay){this.calGo(next,"year",next).then(()=>this.calRestoreFocus(true));return}
|
|
243
|
-
this.calFocus={day:next,minutes:null,event:null};this.calRestoreFocus(true)}});Object.assign(DataGrid.prototype,{calPaint(options={}){if(!this.calBody||this.destroyed)return;this.calDisposeEventRenderers();const owned=this.calBody.contains(document.activeElement)&&!this.calMore;this.calCloseMore(false);this.calHideHover();const scroll=options.keep?this.calScrollState?.():null;const view=this.calView;this.host.setAttribute("data-cx-calendar-view",view);if(view==="month")this.calPaintMonth();else if(view==="mini")this.calPaintMini();else if(view==="months")this.calPaintMonths();else if(view==="weekList"||view==="dayList")this.calPaintList();else if(view==="week"||view==="day"||view==="days")this.calPaintTime();else if(view==="agenda")this.calPaintAgenda();else if(view==="year")this.calPaintYear();else this.calPaintTimeline(options);const restored=scroll&&!options.resize&&this.calRestoreScroll(scroll);if(!restored&&view==="agenda")this.calScrollAgenda();this.calMarkSelection();this.calRestoreFocus(owned)
|
|
263
|
+
this.calFocus={day:next,minutes:null,event:null};this.calRestoreFocus(true)}});Object.assign(DataGrid.prototype,{calPaint(options={}){if(!this.calBody||this.destroyed)return;this.calDisposeEventRenderers();const independent=document.activeElement?.closest(".cx-cal-rpick");const resourceControl=independent&&this.calBody.contains(independent)?document.activeElement.classList.contains("cx-cal-rpick-prev")?".cx-cal-rpick-prev":document.activeElement.classList.contains("cx-cal-rpick-next")?".cx-cal-rpick-next":".cx-cal-rpick-select":null;const owned=this.calBody.contains(document.activeElement)&&!this.calMore&&!independent;this.calCloseMore(false);this.calHideHover();const scroll=options.keep?this.calScrollState?.():null;const view=this.calView;this.host.setAttribute("data-cx-calendar-view",view);if(view==="month")this.calPaintMonth();else if(view==="mini")this.calPaintMini();else if(view==="months")this.calPaintMonths();else if(view==="weekList"||view==="dayList")this.calPaintList();else if(view==="week"||view==="day"||view==="days")this.calPaintTime();else if(view==="agenda")this.calPaintAgenda();else if(view==="year")this.calPaintYear();else this.calPaintTimeline(options);const restored=scroll&&!options.resize&&this.calRestoreScroll(scroll);if(!restored&&view==="agenda")this.calScrollAgenda();this.calMarkSelection();this.calRestoreFocus(owned);if(resourceControl){const target=this.calBody.querySelector(resourceControl);(target?.disabled?this.calBody.querySelector(".cx-cal-rpick-select"):target)?.focus({preventScroll:true})}},calEventTarget(id,preferred){const anchor=preferred?.closest?.(".cx-cal-ev");if(anchor?.dataset.cxEvent===id&&this.calBody.contains(anchor))return anchor;const elements=[...this.calBody.querySelectorAll('[data-cx-event="'+CSS.escape(id)+ '"]')],focus=this.calFocus;const assignments=focus?.event===id&&focus.resource!=null?elements.filter(element=>element._calEvent?.resource===focus.resource):elements;return assignments.find(element=>Number(element.closest("[data-cx-day]")?.dataset.cxDay)===focus?.day)||assignments[0]||elements[0]||null},calFocusTarget(){const focus=this.calFocus,body=this.calBody;if(focus?.event){const element=this.calEventTarget(focus.event);if(element)return element}
|
|
244
264
|
if(focus?.unit!=null&&focus.resource!=null){const element=body.querySelector('.cx-tl-cell[data-cx-unit="'+focus.unit+'"][data-cx-resource="'+CSS.escape(focus.resource)+ '"]');if(element)return element}
|
|
245
265
|
if(focus?.resource!=null&&this.calView==="timeline"&&this.tlAxis){const unit=this.tlAxis.units.findIndex(item=>item.first<=focus.day&&focus.day<=item.last);const element=body.querySelector('.cx-tl-cell[data-cx-unit="'+Math.max(0,unit)+ '"][data-cx-resource="'+CSS.escape(focus.resource)+ '"]');if(element)return element}
|
|
246
266
|
if(focus?.agendaRow){const element=body.querySelector('[data-cx-event-row="'+CSS.escape(focus.agendaRow)+ '"]');if(element)return element}
|
|
247
267
|
const resource=focus?.resource!=null?'[data-cx-resource="'+CSS.escape(focus.resource)+ '"]':"";if(focus&&focus.minutes!=null){const slot=body.querySelector('.cx-cal-slot[data-cx-day="'+focus.day+'"][data-cx-minutes="'+focus.minutes+'"]'+resource);if(slot)return slot}
|
|
248
268
|
if(focus&&focus.minutes==null){const cell=body.querySelector('.cx-cal-allcell[data-cx-day="'+focus.day+'"]'+resource);if(cell)return cell}
|
|
249
269
|
if(focus){const day=body.querySelector('.cx-cal-day[data-cx-day="'+focus.day+'"],.cx-cal-yday[data-cx-day="'+focus.day+'"],[data-cx-day="'+focus.day+'"][data-cx-minutes],.cx-cal-arow[data-cx-day="'+focus.day+'"]');if(day)return day}
|
|
250
|
-
return body.querySelector('.cx-cal-day:not(.cx-cal-out):not(.cx-cal-invalid),.cx-cal-yday:not(.cx-cal-invalid),[data-cx-minutes]:not(.cx-cal-invalid),.cx-cal-arow,.cx-tl-cell:not(.cx-cal-invalid)')||null},calRestoreFocus(force){const target=this.calFocusTarget();for(const element of this.calBody.querySelectorAll('[tabindex="0"]'))if(element!==target&&!element.closest(".cx-cal-ylist"))element.tabIndex=-1;if(!target)return;target.tabIndex=0;if(force)target.focus({preventScroll:false})},calFocusIn(event){const element=event.target,eventElement=element.closest(".cx-cal-ev"),cell=element.closest("[data-cx-day]");if(!eventElement&&!cell||element.closest(".cx-cal-ylist"))return;if(eventElement?._calEvent)this.calFocus={day:eventElement._calEvent.sd,minutes:null,event:eventElement._calEvent.id};else if(cell)this.calFocus={day:Number(cell.dataset.cxDay),minutes:cell.dataset.cxMinutes!==undefined?Number(cell.dataset.cxMinutes):null,event:null,resource:cell.dataset.cxResource,agendaRow:cell.dataset.cxEventRow,unit:cell.dataset.cxUnit!==undefined?Number(cell.dataset.cxUnit):null};for(const other of this.calBody.querySelectorAll('[tabindex="0"]'))if(other!==element&&!other.closest(".cx-cal-ylist"))other.tabIndex=-1;if(element.tabIndex<0&&(eventElement||cell))element.tabIndex=0},calKey(event){if(event.defaultPrevented||event.altKey&&!this.calMoving)return;if(this.calRtl&&(event.key==="ArrowLeft"||event.key==="ArrowRight")){const real=event,key=event.key==="ArrowLeft"?"ArrowRight":"ArrowLeft";event=new Proxy(real,{get:(target,name)=>name==="key"?key:typeof target[name]==="function"?target[name].bind(target):target[name]})}
|
|
270
|
+
return body.querySelector('.cx-cal-day:not(.cx-cal-out):not(.cx-cal-invalid),.cx-cal-yday:not(.cx-cal-invalid),[data-cx-minutes]:not(.cx-cal-invalid),.cx-cal-arow,.cx-tl-cell:not(.cx-cal-invalid)')||null},calRestoreFocus(force){const target=this.calFocusTarget();for(const element of this.calBody.querySelectorAll('[tabindex="0"]'))if(element!==target&&!element.closest(".cx-cal-ylist,.cx-cal-rpick"))element.tabIndex=-1;if(!target)return;target.tabIndex=0;if(force)target.focus({preventScroll:false})},calFocusIn(event){const element=event.target,eventElement=element.closest(".cx-cal-ev"),cell=element.closest("[data-cx-day]");if(!eventElement&&!cell||element.closest(".cx-cal-ylist"))return;if(eventElement?._calEvent)this.calFocus={day:cell?Number(cell.dataset.cxDay):eventElement._calEvent.sd,minutes:null,event:eventElement._calEvent.id,resource:eventElement._calEvent.resource};else if(cell)this.calFocus={day:Number(cell.dataset.cxDay),minutes:cell.dataset.cxMinutes!==undefined?Number(cell.dataset.cxMinutes):null,event:null,resource:cell.dataset.cxResource,agendaRow:cell.dataset.cxEventRow,unit:cell.dataset.cxUnit!==undefined?Number(cell.dataset.cxUnit):null};for(const other of this.calBody.querySelectorAll('[tabindex="0"]'))if(other!==element&&!other.closest(".cx-cal-ylist,.cx-cal-rpick"))other.tabIndex=-1;if(element.tabIndex<0&&(eventElement||cell))element.tabIndex=0},calKey(event){if(event.defaultPrevented||event.altKey&&!this.calMoving)return;if(this.calRtl&&(event.key==="ArrowLeft"||event.key==="ArrowRight")){const real=event,key=event.key==="ArrowLeft"?"ArrowRight":"ArrowLeft";event=new Proxy(real,{get:(target,name)=>name==="key"?key:typeof target[name]==="function"?target[name].bind(target):target[name]})}
|
|
251
271
|
if(this.calEditKey?.(event)||this.calMenuKey(event))return;const target=event.target,eventElement=target.closest(".cx-cal-ev"),more=target.closest(".cx-cal-more-btn");if((event.ctrlKey||event.metaKey)&&(event.key==="z"||event.key==="y"||event.key==="Z")){if(!this.cal.editable)return;event.preventDefault();const done=event.key==="y"||event.shiftKey?this.redo():this.undo();if(done===true&&this.cal.editable.commit==="auto"&&this.hasChanges())this.calCommit(this.getChanges().map(change=>change.id),"");return}
|
|
252
272
|
if(eventElement&&(event.key==="Enter"||event.key===" ")){event.preventDefault();this.calOpenEvent(eventElement._calEvent,true,eventElement).catch(error=>this.emit("error",{error}));return}
|
|
253
273
|
if(more&&(event.key==="Enter"||event.key===" ")){event.preventDefault();this.calOpenMore(Number(more.dataset.cxDay),this.calEventsOn(Number(more.dataset.cxDay)),more);return}
|
|
@@ -262,8 +282,8 @@ if(this.calView==="timeline"){this.calTimelineClick?.(event);return}
|
|
|
262
282
|
if(target.closest(".cx-cal-invalid"))return;const number=target.closest(".cx-cal-num,.cx-cal-dayhead,.cx-cal-yday");if(number&&this.cal.navLinks&&this.cal.views.includes("day")&&!this.calPhone&&this.calView!=="mini"&&target.closest(".cx-cal-num,.cx-cal-dayhead")){const day=Number(number.closest("[data-cx-day]")?.dataset.cxDay??number.dataset.cxDay);if(Number.isFinite(day)){this.calGo(day,"day",true);return}}
|
|
263
283
|
const cell=target.closest("[data-cx-day]");if(!cell)return;const day=Number(cell.dataset.cxDay);if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true))return;if(this.calView==="year"){if(this.cal.views.includes("day"))this.calGo(day,"day",true);else this.calFocusDay(day);return}
|
|
264
284
|
if(cell.dataset.cxMinutes!==undefined){const minutes=Number(cell.dataset.cxMinutes);this.calSelectSlots(day,minutes,minutes+this.cal.slotMinutes,cell.dataset.cxResource)}
|
|
265
|
-
else this.calSelectDays(day,day,this.calCellResource(cell));if(this.calPhone&&this.calView==="month"||this.calView==="mini")this.calFocusDay(day);else if(this.cal.create&&!cell.classList.contains("cx-cal-invalid"))this.calCreateFromClick?.(cell)?.catch?.(error=>this.emit("error",{error}))},calCellResource(cell){return this.tlSolo&&cell.classList.contains("cx-tl-cell")?null:cell.dataset.cxResource??null},calSelectDays(first,last,resource){const zone=this.calZone;if(!this.calAllowsSpan(first*CAL_DAY,(last+1)*CAL_DAY,true))return false;this.calSelection={first,last,from:zone.start(first),to:zone.start(last+1),resource:resource??null};this.calReportSelection(this.calFmt.dayRange(first,last))},calSelectSlots(day,fromMinutes,toMinutes,resource){const zone=this.calZone,from=zone.instant(day,fromMinutes),to=zone.instant(day,toMinutes);if(!this.calAllowsSpan(from,to,false))return false;this.calSelection={first:day,last:day,from,to,fromMinutes,toMinutes,resource:resource??null};this.calReportSelection(this.calFmt.longDay(day)+ " "+fill(this.calLabels.calendarTimeRange,{start:this.calFmt.time(from),end:this.calFmt.time(to)}))},calReportSelection(label){const selection=this.calSelection,events=this.calEvents.filter(event=>{const span=this.calDisplaySpan(event);return!event.background&&calOverlaps(span.start,span.end,selection.from,selection.to)&&(selection.resource==null||event
|
|
266
|
-
(selection.resource==null||cell.dataset.cxResource===undefined||cell.dataset.cxResource===selection.resource);if(inside)cell.setAttribute("aria-selected","true")}},clearCalendarSelection(){if(!this.calSelection)return;this.calSelection=null;this.calMarkSelection();this.calEmitLink("clear",[],"",null)},calEmitLink(action,events,label,selection){if(this.opts.linkEvents!==true&&!this.linkWatching)return;const keys=[...new Set(events.map(event=>event.key))].slice(0,10000),values=Object.create(null);if(selection?.resource!=null&&this.cal.resource)values[this.cal.resource.id]=[selection.resource];this.emit("linkchange",{view:this.view.id,action,scope:"select",keys,values,label,range:selection?{column:this.cal.start.id,from:calIso(selection.from),to:calIso(selection.to)}:null})}});const CAL_CACHE=12,CAL_FALLBACK_PAGES=10;Object.assign(DataGrid.prototype,{async calRecords(from,to,signal){const query=this.acceptedQuery||gridQuery(this.view,this.view.query||{});if(this.view.source.kind==="inline"&&!this.opts.source){if(this.view.rowData){if(!this.source.decoded)await this.source.query({...query,offset:0,limit:1},{signal});await this.source.decoded}
|
|
285
|
+
else this.calSelectDays(day,day,this.calCellResource(cell));if(this.calPhone&&this.calView==="month"||this.calView==="mini")this.calFocusDay(day);else if(this.cal.create&&!cell.classList.contains("cx-cal-invalid"))this.calCreateFromClick?.(cell)?.catch?.(error=>this.emit("error",{error}))},calCellResource(cell){return this.tlSolo&&cell.classList.contains("cx-tl-cell")?null:cell.dataset.cxResource??null},calSelectDays(first,last,resource){const zone=this.calZone;if(!this.calAllowsSpan(first*CAL_DAY,(last+1)*CAL_DAY,true))return false;this.calSelection={first,last,from:zone.start(first),to:zone.start(last+1),resource:resource??null};this.calReportSelection(this.calFmt.dayRange(first,last))},calSelectSlots(day,fromMinutes,toMinutes,resource){const zone=this.calZone,from=zone.instant(day,fromMinutes),to=zone.instant(day,toMinutes);if(!this.calAllowsSpan(from,to,false))return false;this.calSelection={first:day,last:day,from,to,fromMinutes,toMinutes,resource:resource??null};this.calReportSelection(this.calFmt.longDay(day)+ " "+fill(this.calLabels.calendarTimeRange,{start:this.calFmt.time(from),end:this.calFmt.time(to)}))},calReportSelection(label){const selection=this.calSelection,events=this.calEvents.filter(event=>{const span=this.calDisplaySpan(event);return!event.background&&calOverlaps(span.start,span.end,selection.from,selection.to)&&(selection.resource==null||this.calHasResource(event,selection.resource))});this.calMarkSelection();this.calAnnounce(fill(this.calLabels.calendarSelected,{range:label}));this.calEmitLink("select",events,label,selection)},calMarkSelection(){const selection=this.calSelection;for(const cell of this.calBody.querySelectorAll('[aria-selected="true"]'))cell.setAttribute("aria-selected","false");if(!selection)return;for(const cell of this.calBody.querySelectorAll("[data-cx-day]")){const day=Number(cell.dataset.cxDay),minutes=cell.dataset.cxMinutes;const inside=day>=selection.first&&day<=selection.last&&(minutes===undefined||selection.fromMinutes===undefined||+minutes>=selection.fromMinutes&&+minutes<selection.toMinutes)&&
|
|
286
|
+
(selection.resource==null||cell.dataset.cxResource===undefined||cell.dataset.cxResource===selection.resource);if(inside)cell.setAttribute("aria-selected","true")}},clearCalendarSelection(){if(!this.calSelection)return;this.calSelection=null;this.calMarkSelection();this.calEmitLink("clear",[],"",null)},calEmitLink(action,events,label,selection){if(this.opts.linkEvents!==true&&!this.linkWatching)return;const keys=[...new Set(events.filter(event=>!event.base?.external).map(event=>event.key))].slice(0,10000),values=Object.create(null);if(selection?.resource!=null&&this.cal.resource)values[this.cal.resource.id]=[selection.resource];this.emit("linkchange",{view:this.view.id,action,scope:"select",keys,values,label,range:selection?{column:this.cal.start.id,from:calIso(selection.from),to:calIso(selection.to)}:null})}});const CAL_CACHE=12,CAL_FALLBACK_PAGES=10;Object.assign(DataGrid.prototype,{async calRecords(from,to,signal){const query=this.acceptedQuery||gridQuery(this.view,this.view.query||{});if(this.view.source.kind==="inline"&&!this.opts.source){if(this.view.rowData){if(!this.source.decoded)await this.source.query({...query,offset:0,limit:1},{signal});await this.source.decoded}
|
|
267
287
|
const rows=this.view.rows,key=gridResultShape(query);if(this.calRowsCache?.rows!==rows||this.calRowsCache.key!==key||this.calRowsCache.source!==this.source||this.calRowsCache.shape!==rows.length+":"+this.calVersion){const plain=!query.search&&!query.filters.length&&!query.filterGroup;this.calRowsCache={rows,key,source:this.source,shape:rows.length+":"+this.calVersion,matched:plain?rows:gridQueryRows(this.view,query,new Map(this.view.columns.map(column=>[column.id,column])),null)}}
|
|
268
288
|
const matched=this.calEdited(this.calRowsCache.matched);return{rows:matched,overrides:this.calInlineOverrides(matched,from,to),truncated:false}}
|
|
269
289
|
if(this.cal.allDay){from -=CAL_DAY;to +=CAL_DAY}
|
|
@@ -274,9 +294,9 @@ answer||=await this.calFallbackRequest(query,from,to,resources,signal);answer=aw
|
|
|
274
294
|
return out},calRangeRequest(query,from,to,resources,signal){const cal=this.cal,request={protocol:PROTOCOL,type:"gridRangeQuery",view:this.view.id,query:{...query,offset:0,limit:Math.min(10000,cal.limit)},from:calIso(from),to:calIso(to),start:cal.start.id,calendarView:this.calView,limit:Math.min(10000,cal.limit)};for(const name of["end","recurrence","seriesEnd","resource"])if(cal[name])request[name]=cal[name].id;if(resources&&cal.resource)request.resources=resources;return this.runGridHook(hook=>this.source.range(gridFreeze(request),{signal:hook}),signal).then(page=>{checkDocument(page,"gridRangePage");if(page.view!==this.view.id||typeof page.revision!=="string"||!page.revision||!Array.isArray(page.items)||!validStructuralCount(page.matched)||page.items.length>request.limit||page.items.length>page.matched||
|
|
275
295
|
typeof page.truncated!=="boolean"||page.truncated!==page.items.length<page.matched)
|
|
276
296
|
throw new Error("CanopyX: the range page does not answer its query.");if(query.revision!==undefined&&page.revision!==query.revision)throw new Error("CanopyX: grid page snapshot changed.");validateGridRows(page.items,this.view.columns);return{rows:page.items,truncated:page.truncated,matched:page.matched,revision:page.revision}})},async calFallbackRequest(query,from,to,resources,signal){const cal=this.cal,iso=value=>calIso(value),groups=[];const plain={operator:"or",filters:[{column:cal.start.id,operator:"ge",values:[iso(from)]}],groups:[]};if(cal.end)plain.filters.push({column:cal.end.id,operator:"gt",values:[iso(from)]});if(cal.recurrence){const series={operator:"and",filters:[{column:cal.recurrence.id,operator:"isNotNull",values:[]},{column:cal.recurrence.id,operator:"ne",values:[""]}],groups:[]};if(cal.seriesEnd)series.groups.push({operator:"or",filters:[{column:cal.seriesEnd.id,operator:"isNull",values:[]},{column:cal.seriesEnd.id,operator:"gt",values:[iso(from)]}],groups:[]});plain.groups.push(series)}
|
|
277
|
-
groups.push(plain);if(query.filterGroup)groups.push(query.filterGroup);const filters=[{column:cal.start.id,operator:"lt",values:[iso(to)]}];if(resources&&cal.resource)filters.push({column:cal.resource.id,operator:"in",values:resources});const input={...query,cursor:undefined,offset:undefined,sort:cal.start.sortable?[{column:cal.start.id,direction:"asc"}]:[],filterGroup:{operator:"and",filters,groups}};return this.calQueryPages(input,signal,{pages:CAL_FALLBACK_PAGES,rows:cal.limit})},async calReload(options={}){if(this.destroyed||!this.calBody)return;const epoch=++this.calEpoch,range=this.calRange=this.calRangeOf(this.calView,this.calDate);this.calAnswer=null;this.calSyncBar(!!options.announce);const from=this.calZone.start(range.firstDay),to=this.calZone.start(range.lastDay+1);const lifetime=this.calLifetime=new AbortController(),signal=AbortSignal.any?AbortSignal.any([lifetime.signal,this.dataLifetime.signal]):lifetime.signal;this.calPrevLifetime?.abort();this.calPrevLifetime=lifetime;let answer;const slow=setTimeout(()=>{if(epoch===this.calEpoch)this.calNotice(this.calLabels.calendarLoading,false)},200);if(this.cal.sources.some(source=>source.ics)&&!this.calIcsLoad)this.calIcsLoad=this.calLoadPart("calendar-ics").then(()=>this.calLoadIcs()).catch(error=>{this.calIcsLoad=null;throw error});try{answer=await this.calRecords(from,to,signal);if(this.calIcsLoad)await this.calIcsLoad}
|
|
297
|
+
groups.push(plain);if(query.filterGroup)groups.push(query.filterGroup);const filters=[{column:cal.start.id,operator:"lt",values:[iso(to)]}];if(resources&&cal.resource)filters.push({column:cal.resource.id,operator:"in",values:resources});const input={...query,cursor:undefined,offset:undefined,sort:cal.start.sortable?[{column:cal.start.id,direction:"asc"}]:[],filterGroup:{operator:"and",filters,groups}};return this.calQueryPages(input,signal,{pages:CAL_FALLBACK_PAGES,rows:cal.limit})},async calReload(options={}){if(this.destroyed||!this.calBody)return;const epoch=++this.calEpoch,range=this.calRange=this.calRangeOf(this.calView,this.calDate);this.calAnswer=null;this.calSyncBar(!!options.announce);const from=this.calZone.start(range.firstDay),to=this.calZone.start(range.lastDay+1);const lifetime=this.calLifetime=new AbortController(),signal=AbortSignal.any?AbortSignal.any([lifetime.signal,this.dataLifetime.signal]):lifetime.signal;this.calPrevLifetime?.abort();this.calPrevLifetime=lifetime;let answer;const slow=setTimeout(()=>{if(epoch===this.calEpoch)this.calNotice(this.calLabels.calendarLoading,false)},200);if(this.cal.sources.some(source=>source.ics)&&!this.calIcsLoad)this.calIcsLoad=this.calLoadPart("calendar-ics").then(()=>this.calLoadIcs()).catch(error=>{this.calIcsLoad=null;throw error});let feeds;try{[answer,feeds]=await Promise.all([this.calRecords(from,to,signal),this.calLoadJson(from,to,signal)]);if(this.calIcsLoad)await this.calIcsLoad}
|
|
278
298
|
catch(error){clearTimeout(slow);if(epoch!==this.calEpoch||error.name==="AbortError"||this.destroyed)return;this.calNotice(fill(this.calLabels.calendarLoadError,{error:error.message}),true);this.emit("error",{error});return}
|
|
279
|
-
clearTimeout(slow);if(epoch!==this.calEpoch||signal.aborted||this.destroyed)return;const rows=
|
|
299
|
+
clearTimeout(slow);if(epoch!==this.calEpoch||signal.aborted||this.destroyed)return;const rows=[...answer.rows,...(this.calIcsRows||[]),...feeds.rows];this.calAnswer={rows,query:this.acceptedQuery,source:this.source,signal,epoch};const events=calOccurrences(this,rows,from,to,answer.overrides);this.calAllEvents=events;this.calEvents=this.calShownEvents(events);this.calRanges=calStaticRanges(this,from,to);const sticky=this.calSticky?.at===this.calStickyKey()?this.calSticky.text:"";const truncated=answer.truncated||events.truncated?fill(this.calLabels.calendarTruncated,{count:numberFormat(this.lang).format(events.length)}):"";const imported=[...(this.calIcsErrors||[]),...feeds.errors].join(" ");setText(this.calNoticeElement,[sticky,truncated,imported].filter(Boolean).join(" "));this.calNoticeElement.hidden=!sticky&&!truncated&&!imported;this.calNoticeElement.classList.toggle("cx-cal-error",!!sticky||!!imported);this.calPaint(options);this.host.setAttribute("data-cx-calendar-ready","true");this.emit("calendar",this.calSnapshot())},calNotice(text,error,sticky){this.calSticky=sticky&&text?{text,at:this.calStickyKey()}:text?this.calSticky:null;setText(this.calNoticeElement,text);this.calNoticeElement.hidden=!text;this.calNoticeElement.classList.toggle("cx-cal-error",!!error)},calStickyKey(){return[this.calView,this.calRange?.firstDay,this.calRange?.lastDay,this.calZoom].join("|")}});Object.assign(DataGrid.prototype,{async calQueryPages(query,signal,budget){const rows=[],ids=new Set(),tokens=new Set(),cursor=this.cursor;let next,revision=query.revision,snapshot=null,matched=0,complete=false;while(budget.pages>0&&budget.rows>0){let input=gridQuery(this.view,{...query,limit:Math.min(10000,this.pageSize,budget.rows),revision,offset:cursor?undefined:rows.length,cursor:cursor?next:undefined});if(input.groupBy?.length)input=gridQuery(this.view,{...input,groupBy:[],sort:query.sort||[]});const raw=await this.runGridHook(hook=>this.source.query(input,{signal:hook}),signal);const page=cursor?validateGridCursorPage(raw,this.view,input,snapshot):validateGridPage(raw,this.view,input,snapshot);if(page.offset!==rows.length)throw new Error("CanopyX: calendar pages do not form one result.");for(const row of page.items){if(ids.has(row.id))throw new Error("CanopyX: calendar pages repeat a record.");ids.add(row.id);rows.push(row)}
|
|
280
300
|
budget.pages--;budget.rows -=page.items.length;revision=page.revision;matched=page.matched;snapshot||={revision,total:page.total,matched:page.matched};complete=cursor?page.nextCursor===undefined:rows.length===matched;if(complete)break;if(cursor){next=page.nextCursor;if(tokens.has(next))throw new Error("CanopyX: calendar pages repeat a cursor.");tokens.add(next)}}
|
|
281
301
|
return{rows,matched,revision,truncated:!complete}}});const CAL_FIRST_INSTANT=Date.parse("0001-01-01T00:00:00Z");function calSeriesMasters(view,rows,from,to){if(!view.cal.series||!view.cal.recurrenceId)return[];const out=[];for(const row of rows){const base=calBase(view,row);if(!base?.rule)continue;const duration=Math.max(0,base.end-base.start);out.push({id:base.key,from:Math.max(CAL_FIRST_INSTANT,base.start,from-duration),to})}
|
|
282
302
|
return out}
|
|
@@ -285,12 +305,12 @@ return overrides}
|
|
|
285
305
|
Object.assign(DataGrid.prototype,{calInlineOverrides(rows,from,to){const masters=calSeriesMasters(this,rows,from,to);return masters.length?calOverrideMetadata(this,masters,this.calEdited(this.view.rows)):new Map()},async calHostedOverrides(answer,from,to,signal){const rows=this.calEdited(answer.rows,true),masters=calSeriesMasters(this,rows,from,to);if(!masters.length)return{...answer,rows,overrides:new Map()};if(typeof answer.revision!=="string"||!answer.revision)throw new Error("CanopyX: calendar replacement metadata needs the range revision.");const budget={pages:CAL_FALLBACK_PAGES,rows:this.cal.limit},overrides=new Map(),withheld=new Set();for(let at=0;at<masters.length;at +=1000){const batch=masters.slice(at,at+1000);if(!budget.pages||!budget.rows){for(const master of batch)withheld.add(master.id);continue}
|
|
286
306
|
const lower=Math.min(...batch.map(master=>master.from));const query={search:"",revision:answer.revision,sort:[],filters:[{column:this.cal.series.id,operator:"in",values:batch.map(master=>master.id)},{column:this.cal.recurrenceId.id,operator:"ge",values:[calIso(lower)]},{column:this.cal.recurrenceId.id,operator:"lt",values:[calIso(to)]}]};const support=await this.calQueryPages(query,signal,budget);if(support.truncated){for(const master of batch)withheld.add(master.id);continue}
|
|
287
307
|
for(const[id,starts]of calOverrideMetadata(this,batch,this.calEdited(support.rows,true)))overrides.set(id,starts)}
|
|
288
|
-
return{...answer,rows:withheld.size?rows.filter(row=>!withheld.has(row.id)):rows,overrides,truncated:answer.truncated||withheld.size>0}}});Object.assign(DataGrid.prototype,{calParts(){const input=this.view.calendar||{},ranges=Array.isArray(input.ranges)?input.ranges:[],sources=Array.isArray(input.sources)?input.sources:[];const ics=sources.some(source=>source&&source.ics!==undefined),needs=[];if(input.recurrence!==undefined||ics||ranges.some(range=>range&&range.recurrence!==undefined))needs.push("calendar-recurrence");if(input.editable||input.create||input.drop)needs.push("calendar-edit");if(ics)needs.push("calendar-ics");return Promise.all(needs.map(name=>this.calLoadPart(name)))},calLoadPart(name){return loadPart(DataGrid.prototype.parts[name])},calExportFormat(){const grid=this;const write=this.calIcsFormatWriter=async(capture,context)=>{await grid.calLoadPart("calendar-ics");return grid.calIcsWrite(capture,context)};return{id:"ics",label:this.calLabels.calendarExport,extension:"ics",mime:"text/calendar",write}},calIcsExportColumns(explicit){const mapped=new Set(Object.values(this.cal)),columns=this.columns.filter(column=>mapped.has(column));if(columns.some(column=>column.exportable===false))throw new Error("CanopyX: iCalendar export needs every mapped calendar column to allow export.");const ids=columns.map(column=>column.id);if(explicit!==undefined&&(!Array.isArray(explicit)||ids.some(id=>!explicit.includes(id))))
|
|
308
|
+
return{...answer,rows:withheld.size?rows.filter(row=>!withheld.has(row.id)):rows,overrides,truncated:answer.truncated||withheld.size>0}}});Object.assign(DataGrid.prototype,{editCalendarOccurrence(){return Promise.resolve(false)},calParts(){const input=this.view.calendar||{},ranges=Array.isArray(input.ranges)?input.ranges:[],sources=Array.isArray(input.sources)?input.sources:[];const ics=sources.some(source=>source&&source.ics!==undefined),needs=[];if(input.recurrence!==undefined||ics||sources.some(source=>source?.url)||ranges.some(range=>range&&range.recurrence!==undefined))needs.push("calendar-recurrence");if(input.editable||input.create||input.drop)needs.push("calendar-edit");if(ics)needs.push("calendar-ics");return Promise.all(needs.map(name=>this.calLoadPart(name)))},calLoadPart(name){return loadPart(DataGrid.prototype.parts[name])},calExportFormat(){const grid=this;const write=this.calIcsFormatWriter=async(capture,context)=>{await grid.calLoadPart("calendar-ics");return grid.calIcsWrite(capture,context)};return{id:"ics",label:this.calLabels.calendarExport,extension:"ics",mime:"text/calendar",write}},calIcsExportColumns(explicit){const mapped=new Set(Object.values(this.cal)),columns=this.columns.filter(column=>mapped.has(column));if(columns.some(column=>column.exportable===false))throw new Error("CanopyX: iCalendar export needs every mapped calendar column to allow export.");const ids=columns.map(column=>column.id);if(explicit!==undefined&&(!Array.isArray(explicit)||ids.some(id=>!explicit.includes(id))))
|
|
289
309
|
throw new Error("CanopyX: iCalendar export needs all mapped calendar columns; include their ids in the export columns.");return ids},calPrepareIcsExport(options){const columns=this.calIcsExportColumns(options.columns);return this.prepareExport({...options,columns:options.columns===undefined?columns:options.columns,values:"raw"})},async exportCalendar(options={}){await this.calLoadPart("calendar-ics");return this.calIcsExport(options)}});Object.assign(DataGrid.prototype,{startCalendar(){if(this.cal)return;this.cal=calSettings(this);this.calVersion=1;this.calEpoch=0;this.calRuleErrors=new Set();this.calEvents=[];this.calRanges=[];this.calLabels=calLabelsFor(this.labels);this.calZone=calZone(this.cal.zoneName);this.calFmt=calFormats(this.lang,this.calZone);this.calSeriesZone=calZone(this.cal.zoneFixed&&this.cal.zoneName!=="local"?this.cal.zoneName:"utc");this.calMinSegment=Math.max(15,this.cal.slotMinutes*0.75);this.calView=this.cal.view;this.calZoom=this.cal.zoom;this.calDate=this.calClampDay(this.cal.date??this.calZone.day(Date.now()));this.calCollapsed=new Set();this.defaultState.calendar={view:this.cal.view,date:null,zoom:this.cal.zoom};const signal=this.lifetime.signal,host=this.host;host.classList.add("cx-calendar");if(this.cal.toolbar===false)host.classList.add("cx-cal-bare");else if(this.cal.toolbar!==true){for(const item of CAL_TOOLBAR)if(!this.cal.toolbar.has(item))host.classList.add("cx-cal-no-"+item);if(!this.cal.toolbar.has("more")&&this.overflow){this.placeMenus?.(false);this.overflow.details.remove();this.overflow=null}}
|
|
290
|
-
host.setAttribute("data-cx-event-style",this.cal.eventStyle);const root=this.calRoot=h("div","cx-cal");root.setAttribute("role","region");root.setAttribute("aria-label",this.view.label);this.calNoticeElement=h("div","cx-cal-notice");this.calNoticeElement.hidden=true;this.calNoticeElement.setAttribute("role","status");this.calBody=h("div","cx-cal-body");this.calLive=h("div","cx-live cx-sr-only");this.calLive.setAttribute("aria-live","polite");root.append(...[this.calBuildBar(),this.calBuildLegend(),this.calNoticeElement,this.calBody,this.calLive].filter(Boolean));this.grid.before(root);this.calRtl=getComputedStyle(host).direction==="rtl";host.classList.toggle("cx-cal-rtl",this.calRtl);this.calPhone=host.clientWidth>0&&host.clientWidth<=640;if(this.calPhone&&!this.initialState?.calendar)this.calView=this.cal.phone||(this.cal.views.includes("agenda")?"agenda":this.calView);this.calBody.addEventListener("keydown",event=>this.calKey(event),{signal});this.calBody.addEventListener("click",event=>this.calClick(event),{signal});this.calBody.addEventListener("focusin",event=>this.calFocusIn(event),{signal});this.calBody.addEventListener("pointerover",event=>this.calHoverOver(event),{signal});this.calBody.addEventListener("pointerout",event=>this.calHoverOut(event),{signal});for(const type of["pointerdown","keydown","wheel"])this.calBody.addEventListener(type,()=>this.calHideHover(),{signal,passive:true});this.calMenuStart();host.addEventListener("canopyx:query",()=>{const shape=gridResultShape(this.acceptedQuery||{}),same=this.calQueryShape===shape;this.calQueryShape=shape;this.calCache?.clear();this.calReload(same?{keep:true}:{})},{signal});host.addEventListener("canopyx:rowsChanged",()=>{this.calCache?.clear();this.calVersion++;this.calReload({keep:true})},{signal});host.addEventListener("canopyx:commit",()=>{this.calCache?.clear();this.calVersion++;this.calReload({keep:true})},{signal});host.addEventListener("canopyx:cellValueChanged",()=>{this.calVersion++;this.calReload({keep:true})},{signal});if(this.cal.editable||this.cal.create||this.cal.drop){host.setAttribute("data-cx-calendar-editable","true");this.calEditStart?.();this.calBody.addEventListener("pointerdown",event=>this.calDragStart(event),{signal});this.calBody.addEventListener("pointermove",event=>this.calDragMove(event),{signal});this.calBody.addEventListener("pointerup",event=>this.calDragEnd(event,false),{signal});this.calBody.addEventListener("pointercancel",event=>this.calDragEnd(event,true),{signal});this.calBody.addEventListener("lostpointercapture",event=>{if(this.calDrag?.id===event.pointerId&&!this.calDrag.started)this.calDrag=null},{signal})}
|
|
310
|
+
host.setAttribute("data-cx-event-style",this.cal.eventStyle);const root=this.calRoot=h("div","cx-cal");root.setAttribute("role","region");root.setAttribute("aria-label",this.view.label);this.calNoticeElement=h("div","cx-cal-notice");this.calNoticeElement.hidden=true;this.calNoticeElement.setAttribute("role","status");this.calBody=h("div","cx-cal-body");this.calLive=h("div","cx-live cx-sr-only");this.calLive.setAttribute("aria-live","polite");root.append(...[this.calBuildBar(),this.calBuildLegend(),this.calNoticeElement,this.calBody,this.calLive].filter(Boolean));this.grid.before(root);this.calRtl=getComputedStyle(host).direction==="rtl";host.classList.toggle("cx-cal-rtl",this.calRtl);this.calPhone=host.clientWidth>0&&host.clientWidth<=640;if(this.calPhone&&!this.initialState?.calendar)this.calView=this.cal.phone||(this.cal.views.includes("agenda")?"agenda":this.calView);this.calBody.addEventListener("keydown",event=>this.calKey(event),{signal});this.calBody.addEventListener("click",event=>this.calClick(event),{signal});this.calBody.addEventListener("focusin",event=>this.calFocusIn(event),{signal});this.calBody.addEventListener("pointerover",event=>this.calHoverOver(event),{signal});this.calBody.addEventListener("pointerout",event=>this.calHoverOut(event),{signal});for(const type of["pointerdown","keydown","wheel"])this.calBody.addEventListener(type,()=>this.calHideHover(),{signal,passive:true});this.calMenuStart();host.addEventListener("canopyx:query",()=>{const shape=gridResultShape(this.acceptedQuery||{}),same=this.calQueryShape===shape;this.calQueryShape=shape;this.calCache?.clear();this.calJsonCache?.clear();this.calReload(same?{keep:true}:{})},{signal});host.addEventListener("canopyx:rowsChanged",()=>{this.calCache?.clear();this.calVersion++;this.calReload({keep:true})},{signal});host.addEventListener("canopyx:commit",()=>{this.calCache?.clear();this.calVersion++;this.calReload({keep:true})},{signal});host.addEventListener("canopyx:cellValueChanged",()=>{this.calVersion++;this.calReload({keep:true})},{signal});if(this.cal.editable||this.cal.create||this.cal.drop){host.setAttribute("data-cx-calendar-editable","true");this.calEditStart?.();this.calBody.addEventListener("pointerdown",event=>this.calDragStart(event),{signal});this.calBody.addEventListener("pointermove",event=>this.calDragMove(event),{signal});this.calBody.addEventListener("pointerup",event=>this.calDragEnd(event,false),{signal});this.calBody.addEventListener("pointercancel",event=>this.calDragEnd(event,true),{signal});this.calBody.addEventListener("lostpointercapture",event=>{if(this.calDrag?.id===event.pointerId&&!this.calDrag.started)this.calDrag=null},{signal})}
|
|
291
311
|
let width=0,height=0,resizeFrame=0;const resize=()=>{resizeFrame=0;if(this.destroyed||!root.clientWidth)return;if(root.querySelector(":active")){resizeFrame=requestAnimationFrame(resize);return}
|
|
292
312
|
this.updateGridPhone();const phone=host.clientWidth<=640,changed=phone!==this.calPhone||Math.abs(root.clientWidth-width)>1||Math.abs(this.calBody.clientHeight-height)>1;width=root.clientWidth;height=this.calBody.clientHeight;const switched=phone!==this.calPhone;if(switched){this.calPhone=phone;host.classList.toggle("cx-cal-phone",phone);this.calSyncBar(false)}
|
|
293
|
-
if(!changed||!this.calRange)return;if(switched||this.calView==="month")this.calPaint({keep:true,resize:true});else if(this.calView==="timeline")this.tlPaintLanes?.();else if(this.calScroller?.isConnected)this.calGrid.querySelector(".cx-cal-thead").style.paddingInlineEnd=Math.max(0,this.calScroller.offsetWidth-this.calScroller.clientWidth)+ "px"};const observer=new ResizeObserver(()=>{if(!resizeFrame)resizeFrame=requestAnimationFrame(resize)});observer.observe(root);host.classList.toggle("cx-cal-phone",this.calPhone);const timer=setInterval(()=>{if(!this.destroyed&&this.cal.now)this.calTick?.()},60000);const print=on=>{if(this.destroyed||!this.calRange)return;this.calPrinting=on;host.classList.toggle("cx-cal-printing",on);this.tlPrint?.(on);if(this.calView!=="timeline")this.calPaint({keep:true,resize:true})};global.addEventListener("beforeprint",()=>print(true),{signal});global.addEventListener("afterprint",()=>print(false),{signal});signal.addEventListener("abort",()=>{observer.disconnect();cancelAnimationFrame(resizeFrame);clearInterval(timer);clearTimeout(this.calAnnounceTimer);this.calPrevLifetime?.abort();this.calCancelGesture?.();this.calDragEnd?.(null,true);this.calStopMoving?.();if(this.calDropDrag)this.calDropClear?.();this.calDisposeEventRenderers();root.remove();host.classList.remove("cx-calendar","cx-cal-phone","cx-cal-bare","cx-cal-rtl",...CAL_TOOLBAR.map(item=>"cx-cal-no-"+item));this.calHideHover();this.calCloseDetails(false);this.calCloseMenu(false);this.calCloseCreate?.(false);this.calCloseAsk?.(null);clearTimeout(this.calPressTimer);host.removeAttribute("data-cx-event-style");host.removeAttribute("data-cx-calendar-ready")},{once:true});calWrapHighlight();const plugin={name:"calendar-export",exportFormats:[this.calExportFormat()]};this.plugins=[...this.plugins,plugin];this.opts={...this.opts,plugins:[...this.opts.plugins||[],plugin]};this.calRange=this.calRangeOf(this.calView,this.calDate);this.calSyncBar(false);if(this.cal.resources||this.calView==="timeline")return this.calLoadTimeline()},calRangeOf(view,date){const cal=this.cal;if(view==="month"||view==="mini"){const month=calMonthStart(date),end=calAddMonths(month,1)- 1,first=calWeekStart(month,cal.firstDay);return{firstDay:first,lastDay:view==="mini"||cal.fixedWeeks?first+41:calWeekStart(end,cal.firstDay)+ 6,month}}
|
|
313
|
+
if(!changed||!this.calRange)return;if(switched||this.calView==="month"||this.calTimeWindow)this.calPaint({keep:true,resize:true});else if(this.calView==="timeline")this.tlPaintLanes?.();else if(this.calScroller?.isConnected)this.calGrid.querySelector(".cx-cal-thead").style.paddingInlineEnd=Math.max(0,this.calScroller.offsetWidth-this.calScroller.clientWidth)+ "px"};const observer=new ResizeObserver(()=>{if(!resizeFrame)resizeFrame=requestAnimationFrame(resize)});observer.observe(root);host.classList.toggle("cx-cal-phone",this.calPhone);const timer=setInterval(()=>{if(!this.destroyed&&this.cal.now)this.calTick?.()},60000);const print=on=>{if(this.destroyed||!this.calRange)return;this.calPrinting=on;host.classList.toggle("cx-cal-printing",on);this.tlPrint?.(on);if(this.calView!=="timeline")this.calPaint({keep:true,resize:true})};global.addEventListener("beforeprint",()=>print(true),{signal});global.addEventListener("afterprint",()=>print(false),{signal});signal.addEventListener("abort",()=>{observer.disconnect();cancelAnimationFrame(resizeFrame);clearInterval(timer);clearTimeout(this.calAnnounceTimer);this.calPrevLifetime?.abort();this.calCancelGesture?.();this.calDragEnd?.(null,true);this.calStopMoving?.();if(this.calDropDrag)this.calDropClear?.();this.calDisposeEventRenderers();root.remove();host.classList.remove("cx-calendar","cx-cal-phone","cx-cal-bare","cx-cal-rtl",...CAL_TOOLBAR.map(item=>"cx-cal-no-"+item));this.calHideHover();this.calCloseDetails(false);this.calCloseMenu(false);this.calCloseCreate?.(false);this.calCloseAsk?.(null);this.calCloseOccurrence?.(false);clearTimeout(this.calPressTimer);host.removeAttribute("data-cx-event-style");host.removeAttribute("data-cx-calendar-ready")},{once:true});calWrapHighlight();const plugin={name:"calendar-export",exportFormats:[this.calExportFormat()]};this.plugins=[...this.plugins,plugin];this.opts={...this.opts,plugins:[...this.opts.plugins||[],plugin]};this.calRange=this.calRangeOf(this.calView,this.calDate);this.calSyncBar(false);if(this.cal.resources||this.calView==="timeline")return this.calLoadTimeline()},calRangeOf(view,date){const cal=this.cal;if(view==="month"||view==="mini"){const month=calMonthStart(date),end=calAddMonths(month,1)- 1,first=calWeekStart(month,cal.firstDay);return{firstDay:first,lastDay:view==="mini"||cal.fixedWeeks?first+41:calWeekStart(end,cal.firstDay)+ 6,month}}
|
|
294
314
|
if(view==="months"){const month=calMonthStart(date);return{firstDay:month,lastDay:calAddMonths(month,cal.months)- 1,month}}
|
|
295
315
|
if(view==="week"||view==="weekList"){const first=calWeekStart(date,cal.firstDay);return{firstDay:first,lastDay:first+6}}
|
|
296
316
|
if(view==="day"||view==="dayList")return{firstDay:date,lastDay:date};if(view==="days"){let last=date;for(let shown=0;shown<this.calDaysCount();last++)if(!cal.hidden.has(calDow(last)))shown++;return{firstDay:date,lastDay:last-1}}
|
|
@@ -300,7 +320,7 @@ const month=calMonthStart(date);return{firstDay:month,lastDay:calAddMonths(month
|
|
|
300
320
|
if(view==="year"){const parts=calDayParts(date);return{firstDay:calDayNumber(parts.y,1,1),lastDay:calDayNumber(parts.y+1,1,1)- 1}}
|
|
301
321
|
return this.calTimelineRange(date)},calShownDays(first,last){const days=[];for(let day=first;day<=last;day++)if(!this.cal.hidden.has(calDow(day)))days.push(day);return days},calShownDay(day,direction){for(let i=0;i<7&&this.cal.hidden.has(calDow(day));i++)day +=direction||1;return day},calendarStep(direction){if(direction!==1&&direction!==-1)throw new Error("CanopyX: calendarStep takes 1 or -1.");const next=this.calStepDate(direction);if(!this.calInValid(this.calRangeOf(this.calView,next)))return Promise.resolve(this.getCalendar());return this.calGo(next,this.calView)},calInValid(range){const valid=this.cal.validRange;return!valid||range.lastDay>=valid.first&&range.firstDay<=valid.last},calClampDay(day){const valid=this.cal.validRange;return valid?Math.min(Math.max(day,valid.first),valid.last):day},calStepDate(direction){const date=this.calDate,view=this.calView;let next;const span=view==="agenda"?this.cal.agendaSpan:null;if(view==="month"||view==="mini"||span==="month")next=calAddMonths(date,direction);else if(view==="months")next=calAddMonths(date,this.cal.months*direction);else if(view==="year"||span==="year")next=calAddMonths(date,12*direction);else if(view==="week"||view==="weekList"||span==="week")next=date+7*direction;else if(view==="day"||view==="dayList"||span==="day")next=this.calShownDay(date+direction,direction);else if(view==="days"){const range=this.calRangeOf("days",date);next=direction>0?range.lastDay+1:this.calStepBack(date)}
|
|
302
322
|
else next=this.calTimelineStep(date,direction);return next},calStepBack(date){let day=date;for(let shown=0;shown<this.calDaysCount();){day--;if(!this.cal.hidden.has(calDow(day)))shown++}
|
|
303
|
-
return day},calendarToday(){return this.calGo(this.calClampDay(this.calZone.day(Date.now())),this.calView,true)},setCalendarDate(date){const day=typeof date==="number"?date:calParseDay(String(date).slice(0,10));if(!Number.isFinite(day))throw new Error("CanopyX: a calendar date is yyyy-MM-dd.");return this.calGo(this.calClampDay(day),this.calView,true)},setCalendarView(view){if(!this.cal.views.includes(view))throw new Error("CanopyX: the calendar has no view '"+view+"'.");const date=this.calFocus?.day??this.calDate;return this.calGo(view==="day"||view==="days"?this.calShownDay(date,1):date,view,true)},setCalendarZoom(zoom){if(!this.cal.zooms.includes(zoom))throw new Error("CanopyX: the calendar has no zoom '"+zoom+"'.");this.calZoom=zoom;return this.calGo(this.calDate,this.calView,true)},async calGo(date,view,focus){date=this.calClampDay(date);if(typeof focus==="number")focus=this.calClampDay(focus);const changed=view!==this.calView||date!==this.calDate,shift=date-this.calDate;const day=typeof focus==="number"?focus:!focus&&this.calFocus&&view===this.calView?this.calFocus.day+shift:date;this.calView=view;this.calDate=date;this.calFocus={day,minutes:this.calFocus?.minutes??null,event:null,resource:this.calFocus?.resource??null};if(view==="timeline")await this.calLoadTimeline();await this.calReload({announce:changed,keep:true});if(changed)this.stateChanged();return this.getCalendar()},getCalendar(){calNeed(this);const range=this.calRange||this.calRangeOf(this.calView,this.calDate);return gridFreeze({view:this.calView,date:calDayText(this.calDate),zoom:this.calZoom,from:calDayText(range.firstDay),to:calDayText(range.lastDay),timeZone:this.calZone.name,events:this.calEvents.length,collapsed:[...this.calCollapsed]})},calSnapshot(){return this.getCalendar()},getCalendarEvents(){return gridFreeze(this.calEvents.map(event=>gridFreeze({id:event.id,record:event.key,start:calIso(event.start),end:calIso(event.end),allDay:event.allDay,title:event.title,resource:event.resource,recurring:event.recurring,background:event.background})))},calApplyState(input){const state=input&&typeof input==="object"&&!Array.isArray(input)?input:{};const view=this.cal.views.includes(state.view)?state.view:this.cal.view,zoom=this.cal.zooms.includes(state.zoom)?state.zoom:this.cal.zoom;const saved=typeof state.date==="string"?calParseDay(state.date):NaN,date=this.calClampDay(Number.isFinite(saved)?saved:this.cal.date??this.calZone.day(Date.now()));const collapsed=Array.isArray(state.collapsed)&&state.collapsed.length<=10000?state.collapsed.filter(id=>typeof id==="string"):[];this.calZoom=zoom;this.calCollapsed=new Set(collapsed);this.calApplyCollapsed?.();return this.calGo(date,view,true)}});const CAL_STATE={get:DataGrid.prototype.getState,apply:DataGrid.prototype.applyStatePresentation,zone:DataGrid.prototype.setTimeZone};Object.assign(DataGrid.prototype,{getState(){const state=CAL_STATE.get.call(this);if(this.cal)state.calendar={view:this.calView,date:calDayText(this.calDate),zoom:this.calZoom,...this.calCollapsed.size?{collapsed:[...this.calCollapsed]}:{}};return state},applyStatePresentation(state){const applied=CAL_STATE.apply.call(this,state);if(!this.cal||state.calendar===undefined)return applied;const calendar=this.calApplyState(state.calendar);return Promise.all([applied,calendar]).then(([result])=>result)},setTimeZone(zone){const result=CAL_STATE.zone.call(this,zone);if(this.cal&&!this.cal.zoneFixed&&this.calZone.name!==(this.local?"local":"utc")){this.calZone=calZone(this.local?"local":"utc");this.calFmt=calFormats(this.lang,this.calZone);this.calVersion++;this.calReload({keep:true})}
|
|
323
|
+
return day},calendarToday(){return this.calGo(this.calClampDay(this.calZone.day(Date.now())),this.calView,true)},setCalendarDate(date){const day=typeof date==="number"?date:calParseDay(String(date).slice(0,10));if(!Number.isFinite(day))throw new Error("CanopyX: a calendar date is yyyy-MM-dd.");return this.calGo(this.calClampDay(day),this.calView,true)},setCalendarView(view){if(!this.cal.views.includes(view))throw new Error("CanopyX: the calendar has no view '"+view+"'.");const date=this.calFocus?.day??this.calDate;return this.calGo(view==="day"||view==="days"?this.calShownDay(date,1):date,view,true)},setCalendarZoom(zoom){if(!this.cal.zooms.includes(zoom))throw new Error("CanopyX: the calendar has no zoom '"+zoom+"'.");this.calZoom=zoom;return this.calGo(this.calDate,this.calView,true)},async calGo(date,view,focus){date=this.calClampDay(date);if(typeof focus==="number")focus=this.calClampDay(focus);const changed=view!==this.calView||date!==this.calDate,shift=date-this.calDate;const day=typeof focus==="number"?focus:!focus&&this.calFocus&&view===this.calView?this.calFocus.day+shift:date;this.calView=view;this.calDate=date;this.calFocus={day,minutes:this.calFocus?.minutes??null,event:null,resource:this.calFocus?.resource??null};if(view==="timeline")await this.calLoadTimeline();await this.calReload({announce:changed,keep:true});if(changed)this.stateChanged();return this.getCalendar()},getCalendar(){calNeed(this);const range=this.calRange||this.calRangeOf(this.calView,this.calDate);return gridFreeze({view:this.calView,date:calDayText(this.calDate),zoom:this.calZoom,from:calDayText(range.firstDay),to:calDayText(range.lastDay),timeZone:this.calZone.name,events:this.calEvents.length,collapsed:[...this.calCollapsed]})},calSnapshot(){return this.getCalendar()},getCalendarEvents(){return gridFreeze(this.calEvents.map(event=>gridFreeze({id:event.id,record:event.key,start:calIso(event.start),end:calIso(event.end),allDay:event.allDay,title:event.title,resource:event.resource,resourceIds:this.calEventResources(event),recurring:event.recurring,background:event.background,source:event.base.source,external:event.base.external})))},calApplyState(input){const state=input&&typeof input==="object"&&!Array.isArray(input)?input:{};const view=this.cal.views.includes(state.view)?state.view:this.cal.view,zoom=this.cal.zooms.includes(state.zoom)?state.zoom:this.cal.zoom;const saved=typeof state.date==="string"?calParseDay(state.date):NaN,date=this.calClampDay(Number.isFinite(saved)?saved:this.cal.date??this.calZone.day(Date.now()));const collapsed=Array.isArray(state.collapsed)&&state.collapsed.length<=10000?state.collapsed.filter(id=>typeof id==="string"):[];this.calZoom=zoom;this.calCollapsed=new Set(collapsed);this.calApplyCollapsed?.();return this.calGo(date,view,true)}});const CAL_STATE={get:DataGrid.prototype.getState,apply:DataGrid.prototype.applyStatePresentation,zone:DataGrid.prototype.setTimeZone};Object.assign(DataGrid.prototype,{getState(){const state=CAL_STATE.get.call(this);if(this.cal)state.calendar={view:this.calView,date:calDayText(this.calDate),zoom:this.calZoom,...this.calCollapsed.size?{collapsed:[...this.calCollapsed]}:{}};return state},applyStatePresentation(state){const applied=CAL_STATE.apply.call(this,state);if(!this.cal||state.calendar===undefined)return applied;const calendar=this.calApplyState(state.calendar);return Promise.all([applied,calendar]).then(([result])=>result)},setTimeZone(zone){const result=CAL_STATE.zone.call(this,zone);if(this.cal&&!this.cal.zoneFixed&&this.calZone.name!==(this.local?"local":"utc")){this.calZone=calZone(this.local?"local":"utc");this.calFmt=calFormats(this.lang,this.calZone);this.calVersion++;this.calReload({keep:true})}
|
|
304
324
|
return result}});function calNeed(grid){if(!grid.cal)throw new Error("CanopyX: this grid has no calendar.")}
|
|
305
325
|
for(const name of["setCalendarView","setCalendarDate","setCalendarZoom","calendarStep","calendarToday","getCalendarEvents","exportCalendar","openCalendarEvent","clearCalendarSelection","setCalendarSource"]){const method=DataGrid.prototype[name];if(method)DataGrid.prototype[name]=function(...args){calNeed(this);return method.apply(this,args)}}
|
|
306
326
|
function calWrapHighlight(){for(const name of["setHighlight","clearHighlight"]){const current=DataGrid.prototype[name];if(current._calendar)continue;const wrapped=function(...args){const result=current.apply(this,args);if(this.cal)Promise.resolve(result).then(()=>{calWrapHighlight();this.calMarkHighlights()},()=>{});return result};wrapped._calendar=true;wrapped.linkStub=current.linkStub;DataGrid.prototype[name]=wrapped}}
|