@evotecit/canopyx 0.3.11 → 0.3.15

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.
Files changed (102) hide show
  1. package/CanopyX.Html/Assets/canopyx-calendar-edit.js +36 -18
  2. package/CanopyX.Html/Assets/canopyx-calendar-ics.js +7 -5
  3. package/CanopyX.Html/Assets/canopyx-calendar.css +124 -116
  4. package/CanopyX.Html/Assets/canopyx-calendar.js +74 -54
  5. package/CanopyX.Html/Assets/canopyx-cell-range.css +8 -8
  6. package/CanopyX.Html/Assets/canopyx-cell-range.js +1 -1
  7. package/CanopyX.Html/Assets/canopyx-changes.css +113 -113
  8. package/CanopyX.Html/Assets/canopyx-changes.js +7 -10
  9. package/CanopyX.Html/Assets/canopyx-column-groups.css +5 -5
  10. package/CanopyX.Html/Assets/canopyx-column-groups.js +1 -1
  11. package/CanopyX.Html/Assets/canopyx-data.js +2 -2
  12. package/CanopyX.Html/Assets/canopyx-detail-rows.css +9 -9
  13. package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
  14. package/CanopyX.Html/Assets/canopyx-editing.css +18 -18
  15. package/CanopyX.Html/Assets/canopyx-editing.js +1 -1
  16. package/CanopyX.Html/Assets/canopyx-enhance.js +2 -2
  17. package/CanopyX.Html/Assets/canopyx-export.js +8 -4
  18. package/CanopyX.Html/Assets/canopyx-facets.css +33 -33
  19. package/CanopyX.Html/Assets/canopyx-facets.js +10 -11
  20. package/CanopyX.Html/Assets/canopyx-filter-editor.css +57 -57
  21. package/CanopyX.Html/Assets/canopyx-filter-editor.js +15 -11
  22. package/CanopyX.Html/Assets/canopyx-flow-checks.css +11 -11
  23. package/CanopyX.Html/Assets/canopyx-flow-checks.js +1 -1
  24. package/CanopyX.Html/Assets/canopyx-flow-command.css +7 -7
  25. package/CanopyX.Html/Assets/canopyx-flow-command.js +1 -1
  26. package/CanopyX.Html/Assets/canopyx-flow-dialog.css +7 -7
  27. package/CanopyX.Html/Assets/canopyx-flow-dialog.js +1 -1
  28. package/CanopyX.Html/Assets/canopyx-flow-drafts.css +7 -7
  29. package/CanopyX.Html/Assets/canopyx-flow-drafts.js +1 -1
  30. package/CanopyX.Html/Assets/canopyx-flow-entry.css +17 -76
  31. package/CanopyX.Html/Assets/canopyx-flow-entry.js +9 -3
  32. package/CanopyX.Html/Assets/canopyx-flow-host.css +4 -4
  33. package/CanopyX.Html/Assets/canopyx-flow-host.js +1 -1
  34. package/CanopyX.Html/Assets/canopyx-flow-module.mjs +9 -3
  35. package/CanopyX.Html/Assets/canopyx-flow-pick.css +4 -4
  36. package/CanopyX.Html/Assets/canopyx-flow-pick.js +1 -1
  37. package/CanopyX.Html/Assets/canopyx-flow-preview.css +6 -6
  38. package/CanopyX.Html/Assets/canopyx-flow-preview.js +1 -1
  39. package/CanopyX.Html/Assets/canopyx-flow-records.css +3 -3
  40. package/CanopyX.Html/Assets/canopyx-flow-records.js +1 -1
  41. package/CanopyX.Html/Assets/canopyx-flow-tasks.css +17 -17
  42. package/CanopyX.Html/Assets/canopyx-flow-tasks.js +1 -1
  43. package/CanopyX.Html/Assets/canopyx-flow.css +79 -79
  44. package/CanopyX.Html/Assets/canopyx-flow.js +1 -1
  45. package/CanopyX.Html/Assets/canopyx-format-editor.css +23 -23
  46. package/CanopyX.Html/Assets/canopyx-format-editor.js +2 -3
  47. package/CanopyX.Html/Assets/canopyx-formatting.css +17 -17
  48. package/CanopyX.Html/Assets/canopyx-formatting.js +5 -5
  49. package/CanopyX.Html/Assets/canopyx-forms.css +5 -5
  50. package/CanopyX.Html/Assets/canopyx-forms.js +1 -1
  51. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +134 -77
  52. package/CanopyX.Html/Assets/canopyx-grid-options.css +10 -0
  53. package/CanopyX.Html/Assets/canopyx-grid-options.js +47 -0
  54. package/CanopyX.Html/Assets/canopyx-grid.css +199 -261
  55. package/CanopyX.Html/Assets/canopyx-grid.js +134 -77
  56. package/CanopyX.Html/Assets/canopyx-grouping.css +17 -17
  57. package/CanopyX.Html/Assets/canopyx-grouping.js +12 -9
  58. package/CanopyX.Html/Assets/canopyx-highlight.css +14 -14
  59. package/CanopyX.Html/Assets/canopyx-highlight.js +2 -2
  60. package/CanopyX.Html/Assets/canopyx-interactive.css +94 -155
  61. package/CanopyX.Html/Assets/canopyx-interactive.js +58 -24
  62. package/CanopyX.Html/Assets/canopyx-labels-de.js +5 -0
  63. package/CanopyX.Html/Assets/canopyx-labels-es.js +5 -0
  64. package/CanopyX.Html/Assets/canopyx-labels-fr.js +5 -0
  65. package/CanopyX.Html/Assets/canopyx-labels-it.js +5 -0
  66. package/CanopyX.Html/Assets/canopyx-labels-pl.js +5 -0
  67. package/CanopyX.Html/Assets/canopyx-layouts.css +17 -17
  68. package/CanopyX.Html/Assets/canopyx-layouts.js +4 -12
  69. package/CanopyX.Html/Assets/canopyx-link.css +7 -7
  70. package/CanopyX.Html/Assets/canopyx-link.js +1 -1
  71. package/CanopyX.Html/Assets/canopyx-live.css +9 -9
  72. package/CanopyX.Html/Assets/canopyx-live.js +2 -2
  73. package/CanopyX.Html/Assets/canopyx-matrix.css +74 -74
  74. package/CanopyX.Html/Assets/canopyx-matrix.js +3 -4
  75. package/CanopyX.Html/Assets/canopyx-module.mjs +58 -24
  76. package/CanopyX.Html/Assets/canopyx-pager.css +2 -2
  77. package/CanopyX.Html/Assets/canopyx-pager.js +1 -1
  78. package/CanopyX.Html/Assets/canopyx-presenters.js +11 -0
  79. package/CanopyX.Html/Assets/canopyx-record-panel.css +49 -49
  80. package/CanopyX.Html/Assets/canopyx-record-panel.js +13 -11
  81. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +142 -84
  82. package/CanopyX.Html/Assets/canopyx-reporting.css +214 -276
  83. package/CanopyX.Html/Assets/canopyx-reporting.js +142 -84
  84. package/CanopyX.Html/Assets/canopyx-timeline.css +28 -28
  85. package/CanopyX.Html/Assets/canopyx-timeline.js +11 -10
  86. package/CanopyX.Html/Assets/canopyx-tree-tools.css +47 -46
  87. package/CanopyX.Html/Assets/canopyx-tree-tools.js +5 -4
  88. package/CanopyX.Html/Assets/canopyx.css +81 -142
  89. package/CanopyX.Html/Assets/canopyx.js +58 -24
  90. package/JavaScript/calendar.d.ts +15 -0
  91. package/JavaScript/grid.d.ts +29 -4
  92. package/JavaScript/hosts.d.ts +11 -0
  93. package/JavaScript/locales/de.mjs +5 -0
  94. package/JavaScript/locales/es.mjs +5 -0
  95. package/JavaScript/locales/fr.mjs +5 -0
  96. package/JavaScript/locales/it.mjs +5 -0
  97. package/JavaScript/locales/pl.mjs +5 -0
  98. package/JavaScript/plugins.d.ts +4 -0
  99. package/JavaScript/protocol.d.ts +14 -1
  100. package/JavaScript/records.d.ts +2 -0
  101. package/README.md +6 -2
  102. package/package.json +7 -1
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"762f4b4b62ecb4d8",install(context){if(installed.has(context.DataGrid.prototype))return;const{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.",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)}
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.ics!==undefined&&!calSameOrigin(item.ics)||item.hidden!==undefined&&typeof item.hidden!=="boolean")
63
- calInvalid("a calendar source has a unique id, a label, and optional tone, ics (a same-origin address) and hidden.");if(item.ics===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,hidden:item.hidden===true}})}
64
- function calSameOrigin(value){if(typeof value!=="string"||!value||value.length>2048)return false;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;
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.resource?calResourceOf(read(cal.resource)):null,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:fields?fields.series||null:cal.series?read(cal.series)||null:null,recurrenceId:calInstant(fields?fields.recurrenceId:read(cal.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))}
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 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)}
87
- function calOccurrences(view,rows,from,to,metadata){const out=[],overrides=new Map([...(metadata||[])].map(([id,starts])=>[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.series))overrides.set(base.series,new Set());overrides.get(base.series).add(base.recurrenceId)}
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.key),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)}}
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.key+"@"+calIso(start):base.key,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}}
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.resource&&this.calResourceName)parts.push(this.calResourceName(event.resource));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(--cx-tone-neutral,var(--cx-muted))":"var(--cx-tone-"+event.tone+",var(--cx-accent))");element.style.setProperty("--cx-ev-bg","var(--cx-tone-"+event.tone+"-bg,color-mix(in srgb,var(--cx-ev) 14%,var(--cx-surface)))")}
122
- element.setAttribute("aria-label",this.calEventName(event));if(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)}
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(kind==="block"||kind==="item"||kind==="lane"){if(event.subtitle){const subtitle=h("span","cx-cal-ev-sub");setText(subtitle,event.subtitle);element.appendChild(subtitle)}
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.calBody.querySelector('[data-cx-event="'+CSS.escape(event.id)+ '"]')||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")
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.calBody.querySelector('[data-cx-event="'+CSS.escape(event.id)+ '"]')||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||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)}
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=!!keys?.has(element.dataset.cxRecord);if(on)count++;if(on)element.dataset.cxLinked="true";else delete element.dataset.cxLinked}
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.resource!=null?(this.calResourceName?this.calResourceName(event.resource):event.resource):"";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)}
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(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)})}
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(--cx-tone-"+source.tone+",var(--cx-accent))");swatch.setAttribute("aria-hidden","true");setText(text,source.label);button.append(swatch,text);button.addEventListener("click",()=>this.calToggleSource(source.id));legend.appendChild(button)}
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 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)}
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||event.resource==null||event.resource===resource))
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","var(--cx-tone-"+backgrounds[0].tone+",var(--cx-tone-info))");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)}}
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","var(--cx-tone-"+item.event.tone+",var(--cx-accent))");dots.appendChild(dot)}
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","var(--cx-tone-"+backgrounds[0].tone+",var(--cx-tone-info))")}
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}));const size=this.calPhone&&!this.calPrinting&&all.length>2?(days.length===1?2:1):all.length,pages=Math.ceil(all.length / size);
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{all,size,pages,page}=this.calResourcePages,bar=h("div","cx-cal-rpick"),select=h("select","cx-cal-rpick-select");select.setAttribute("aria-label",this.calLabels.calendarResources);for(let at=0;at<pages;at++){const option=h("option");option.value=String(at);setText(option,all.slice(at*size,at*size+size).map(item=>item.label).join(", "));select.appendChild(option)}
190
- select.value=String(page);select.addEventListener("change",()=>{this.calResourceFirst=Number(select.value)*size;this.calPaint({keep:true});this.calBody.querySelector(".cx-cal-rpick-select")?.focus()});bar.appendChild(select);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;
191
- 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 perDay=columns.length / days.length;
192
- for(const day of days){const cell=h("div","cx-cal-dayhead" +(day===today?" cx-cal-today":""));cell.setAttribute("role","columnheader");cell.dataset.cxDay=String(day);cell.style.flexGrow=String(perDay);cell.setAttribute("aria-label",fmt.longDay(day));if(perDay>1)cell.setAttribute("aria-colspan",String(perDay));if(!this.calAllowsSpan(day*CAL_DAY,(day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
193
- const name=h("span","cx-cal-dayname"),number=h("span","cx-cal-daynum");setText(name,fmt.weekday(day,this.calPhone?"narrow":"short"));setText(number,fmt.dayNumber(day));cell.append(name,number);if(day===today)cell.setAttribute("aria-current","date");dayRow.appendChild(cell)}
194
- 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.style.flexGrow=String(end-at+1);setText(cell,columns[at].group||"");cell.title=columns[at].group||"";groupRow.appendChild(cell);at=end+1}
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===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))}
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.length / days.length, cells = [];
206
- columns.forEach((column,at)=>{const cell=h("div","cx-cal-allcell");cell.setAttribute("role","gridcell");cell.tabIndex=-1;cell.dataset.cxDay=String(column.day);if(!this.calAllowsSpan(column.day*CAL_DAY,(column.day+1)*CAL_DAY,true)){cell.classList.add("cx-cal-invalid");cell.setAttribute("aria-disabled","true")}
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","var(--cx-tone-"+tone+",var(--cx-tone-info))");if(label&&(bottom-top)*ppm>=18){const text=h("span");setText(text,label);block.appendChild(text)}
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){for(const other of this.calBody.querySelectorAll('[tabindex="0"]'))other.tabIndex=-1;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}
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)},calFocusTarget(){const focus=this.calFocus,body=this.calBody;if(focus?.event){const element=body.querySelector('[data-cx-event="'+CSS.escape(focus.event)+ '"]');if(element)return element}
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.resource===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)&&
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=this.calIcsRows?.length?[...answer.rows,...this.calIcsRows]:answer.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?.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)}
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}}