@evotecit/canopyx 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CanopyX.Html/Assets/canopyx-calendar-edit.js +113 -0
- package/CanopyX.Html/Assets/canopyx-calendar-ics.js +87 -0
- package/CanopyX.Html/Assets/canopyx-calendar-recurrence.js +36 -0
- package/CanopyX.Html/Assets/canopyx-calendar.css +279 -0
- package/CanopyX.Html/Assets/canopyx-calendar.js +307 -0
- package/CanopyX.Html/Assets/canopyx-cell-range.css +38 -0
- package/CanopyX.Html/Assets/canopyx-cell-range.js +141 -0
- package/CanopyX.Html/Assets/canopyx-column-groups.css +18 -0
- package/CanopyX.Html/Assets/canopyx-column-groups.js +51 -0
- package/CanopyX.Html/Assets/canopyx-data.js +14 -0
- package/CanopyX.Html/Assets/canopyx-detail-rows.css +18 -0
- package/CanopyX.Html/Assets/canopyx-detail-rows.js +51 -0
- package/CanopyX.Html/Assets/canopyx-editing.css +44 -0
- package/CanopyX.Html/Assets/canopyx-editing.js +134 -0
- package/CanopyX.Html/Assets/canopyx-enhance.js +126 -0
- package/CanopyX.Html/Assets/canopyx-export.js +18 -0
- package/CanopyX.Html/Assets/canopyx-facets.css +86 -0
- package/CanopyX.Html/Assets/canopyx-facets.js +65 -0
- package/CanopyX.Html/Assets/canopyx-filter-editor.css +15 -18
- package/CanopyX.Html/Assets/canopyx-filter-editor.js +23 -17
- package/CanopyX.Html/Assets/canopyx-format-editor.css +77 -0
- package/CanopyX.Html/Assets/canopyx-format-editor.js +30 -0
- package/CanopyX.Html/Assets/canopyx-formatting.css +40 -0
- package/CanopyX.Html/Assets/canopyx-formatting.js +101 -0
- package/CanopyX.Html/Assets/canopyx-grid-module.mjs +352 -246
- package/CanopyX.Html/Assets/canopyx-grid.css +139 -117
- package/CanopyX.Html/Assets/canopyx-grid.js +352 -246
- package/CanopyX.Html/Assets/canopyx-grouping.css +39 -0
- package/CanopyX.Html/Assets/canopyx-grouping.js +85 -0
- package/CanopyX.Html/Assets/canopyx-highlight.css +34 -0
- package/CanopyX.Html/Assets/canopyx-highlight.js +80 -0
- package/CanopyX.Html/Assets/canopyx-history.js +13 -0
- package/CanopyX.Html/Assets/canopyx-interactive.css +18 -6
- package/CanopyX.Html/Assets/canopyx-interactive.js +179 -100
- package/CanopyX.Html/Assets/canopyx-labels-de.js +304 -0
- package/CanopyX.Html/Assets/canopyx-labels-es.js +304 -0
- package/CanopyX.Html/Assets/canopyx-labels-fr.js +529 -0
- package/CanopyX.Html/Assets/canopyx-labels-it.js +529 -0
- package/CanopyX.Html/Assets/canopyx-labels-pl.js +304 -0
- package/CanopyX.Html/Assets/canopyx-layouts.css +48 -0
- package/CanopyX.Html/Assets/canopyx-layouts.js +39 -0
- package/CanopyX.Html/Assets/canopyx-link.css +19 -0
- package/CanopyX.Html/Assets/canopyx-link.js +38 -0
- package/CanopyX.Html/Assets/canopyx-links.js +14 -0
- package/CanopyX.Html/Assets/canopyx-live.css +24 -0
- package/CanopyX.Html/Assets/canopyx-live.js +88 -0
- package/CanopyX.Html/Assets/canopyx-module.mjs +180 -100
- package/CanopyX.Html/Assets/canopyx-pager.css +10 -0
- package/CanopyX.Html/Assets/canopyx-pager.js +14 -0
- package/CanopyX.Html/Assets/canopyx-record-panel.css +111 -0
- package/CanopyX.Html/Assets/canopyx-record-panel.js +178 -0
- package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +406 -342
- package/CanopyX.Html/Assets/canopyx-reporting.css +140 -135
- package/CanopyX.Html/Assets/canopyx-reporting.js +406 -342
- package/CanopyX.Html/Assets/canopyx-search.js +32 -0
- package/CanopyX.Html/Assets/canopyx-timeline.css +80 -0
- package/CanopyX.Html/Assets/canopyx-timeline.js +88 -0
- package/CanopyX.Html/Assets/canopyx-tree-tools.css +103 -0
- package/CanopyX.Html/Assets/canopyx-tree-tools.js +92 -0
- package/CanopyX.Html/Assets/canopyx.css +17 -5
- package/CanopyX.Html/Assets/canopyx.js +175 -96
- package/JavaScript/calendar.d.ts +362 -0
- package/JavaScript/cell-ranges.d.ts +76 -0
- package/JavaScript/column-groups.d.ts +32 -0
- package/JavaScript/editing.d.ts +257 -0
- package/JavaScript/enhance.d.ts +202 -0
- package/JavaScript/facets.d.ts +58 -0
- package/JavaScript/formatting.d.ts +88 -0
- package/JavaScript/grid.d.ts +409 -56
- package/JavaScript/hosts.d.ts +177 -2
- package/JavaScript/index.d.ts +53 -4
- package/JavaScript/live.d.ts +115 -0
- package/JavaScript/locales/de.mjs +304 -0
- package/JavaScript/locales/es.mjs +304 -0
- package/JavaScript/locales/fr.d.ts +6 -0
- package/JavaScript/locales/fr.mjs +527 -0
- package/JavaScript/locales/it.d.ts +6 -0
- package/JavaScript/locales/it.mjs +527 -0
- package/JavaScript/locales/pl.mjs +304 -0
- package/JavaScript/plugins.d.ts +113 -0
- package/JavaScript/protocol.d.ts +139 -6
- package/JavaScript/records.d.ts +136 -0
- package/README.md +61 -9
- package/README.nuget.md +1 -1
- package/package.json +99 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
(function(global){"use strict";const installed=new WeakSet();const part={version:"505792b52f874c1b",requires:["calendar"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,cellValue,GRID_DRAG}=context;const{CAL_DAY,CAL_GUTTER,CAL_MINUTE,CAL_SLOT_PX,calDayText,calDow,calInAllDayRow,calIso,calOverlaps,calZone,calDayGrid}=DataGrid.prototype.calKit;Object.assign(DataGrid.prototype,{calCanEdit(event,resize){const edit=this.cal.editable;if(!edit||!event||event.background||event.base?.external||this.editReadOnly||!this.edit||this.destroyed)return false;if(resize?!edit.resize||event.marker:!edit.move)return false;const columns=[this.cal.start,resize||!event.marker?this.cal.end:null].filter(Boolean);const record=this.editRecord?.(event.key)||event.row;return columns.every(column=>column.editable&&this.canEditCell(record,column.id))&&(!event.recurring||!!this.cal.recurrence)},calCheck(event,proposal,plan){const edit=this.cal.editable,labels=this.calLabels;const resource=proposal.resource;if(!this.edit||this.destroyed)return labels.calendarUnavailable;const resize=proposal.end-proposal.start!==event.end-event.start;if(!this.calCanEdit(event,resize))return this.edit.labels.gridEditNotSaved;if(resource!==undefined&&resource!==event.resource&&(!edit.resources||!this.cal.resource||
|
|
2
|
+
!this.canEditCell(this.editRecord?.(event.key)||event.row,this.cal.resource.id)))return this.edit.labels.gridEditNotSaved;if(!this.calAllowsSpan(proposal.start,proposal.end,proposal.allDay))return labels.calendarOutsideRange;if(edit.allowed&&resource!=null){const allowed=String(cellValue(event.row.cells[edit.allowed.id])??"").split(/[\s,;]+/).filter(Boolean);
|
|
3
|
+
if(allowed.length&&!allowed.includes(resource))return fill(labels.calendarNotAllowed,{resource:this.calResourceName?this.calResourceName(resource):resource})}
|
|
4
|
+
const spanProblem=this.calSpanProblem(proposal);if(spanProblem)return spanProblem;if(plan)return plan.problem||this.calOccurrenceProblem(event,proposal,plan);if(!edit.overlap){for(const other of this.calEvents){if(other.key===event.key||other.background||(this.cal.resource&&other.resource!==resource))continue;if(other.start<proposal.end&&other.end>proposal.start||other.marker&&other.start>=proposal.start&&other.start<proposal.end)return fill(labels.calendarConflict,{title:other.title})}}
|
|
5
|
+
return null},calSpanProblem(proposal){const labels=this.calLabels,resource=proposal.resource;if(!this.calAllowsSpan(proposal.start,proposal.end,proposal.allDay))return labels.calendarOutsideRange;if(this.cal.editable.working&&!proposal.allDay){const zone=this.calZone,first=zone.day(proposal.start),last=zone.day(Math.max(proposal.start,proposal.end-1));for(let day=first;day<=last;day++){const from=day===first?zone.parts(proposal.start).minutes:0,to=day===last?zone.parts(proposal.end).minutes||1440:1440;const hours=this.calHoursFor?.(resource,calDow(day))??(this.cal.working?(this.cal.working.days.has(calDow(day))?[this.cal.working]:[]):null);if(hours&&!hours.some(span=>from>=span.start&&to<=span.end))return labels.calendarOutsideHours}}
|
|
6
|
+
for(const range of this.calRanges)if(range.kind==="unavailable"&&(range.resource==null||range.resource===resource)&&calOverlaps(proposal.start,proposal.end,range.start,range.end))return labels.calendarUnavailable;return null},calOccurrenceProblem(event,proposal,plan){const items=this.calEvents.filter(other=>!other.background&&(other.key!==event.key||
|
|
7
|
+
plan.mode==="this"&&other.id!==event.id||plan.mode==="following"&&other.start<event.start))
|
|
8
|
+
.map(other=>({...other,changed:false}));items.push(...plan.spans.map(span=>({...span,title:event.title,changed:true})));for(const item of items)if(item.changed){const problem=this.calSpanProblem({...item,allDay:proposal.allDay});if(problem)return problem}
|
|
9
|
+
if(this.cal.editable.overlap)return null;items.sort((a,b)=>a.start-b.start||b.end-a.end);const active=new Map();for(const item of items){const resource=this.cal.resource?item.resource:null,previous=active.get(resource)||{any:null,changed:null};const other=item.changed?previous.any:previous.changed;if(other&&(calOverlaps(item.start,item.end,other.start,other.end)||calOverlaps(other.start,other.end,item.start,item.end)))
|
|
10
|
+
return fill(this.calLabels.calendarConflict,{title:item.changed?other.title:item.title});if(!previous.any||item.end>previous.any.end)previous.any=item;if(item.changed&&(!previous.changed||item.end>previous.changed.end))previous.changed=item;active.set(resource,previous)}
|
|
11
|
+
return null},async calApply(event,proposal){const cal=this.cal,labels=this.calLabels,delta=proposal.start-event.start,decision=this.calEditSnapshot(event);await this.loadEditing();calWrapEditRecord();event=this.calEditCurrent(event,decision);if(!event)return this.calRefuseDecision();const problem=this.calCheck(event,proposal);if(problem){this.calAnnounce(problem);this.calNotice(problem,true,true);return false}
|
|
12
|
+
if(event.recurring&&!this.emit("occurrenceChange",{id:event.key,occurrence:calIso(event.start),start:calIso(proposal.start),end:calIso(proposal.end),resource:proposal.resource??null}))
|
|
13
|
+
return false;let plan=null;if(event.recurring){const mode=await this.calOccurrenceMode(event,proposal,decision);event=this.calEditCurrent(event,decision);if(!event)return this.calRefuseDecision();if(!mode)return false;plan=this.calOccurrencePlan(event,proposal,mode);const currentProblem=this.calCheck(event,proposal,plan);if(currentProblem){this.calAnnounce(currentProblem);this.calNotice(currentProblem,true,true);return false}
|
|
14
|
+
if(plan.mode==="this")return this.calApplyThis(event,proposal,plan,decision);if(plan.mode==="following")return this.calApplyFollowing(event,proposal,plan,decision)}
|
|
15
|
+
const base=event.base,values=plan?plan.values:[[cal.start.id,calIso(proposal.start)]];if(!plan&&cal.end&&(proposal.end!==base.end||proposal.start!==base.start))values.push([cal.end.id,proposal.end>proposal.start||!base.marker?calIso(proposal.end):null]);if(!plan&&cal.resource&&proposal.resource!==undefined&&proposal.resource!==event.resource)values.push([cal.resource.id,proposal.resource]);const record=this.editRecord(event.key),applied=[];if(values.some(([column])=>!this.canEditCell(record,column))){this.calAnnounce(this.edit.labels.gridEditNotSaved);this.calNotice(this.edit.labels.gridEditNotSaved,true,true);return false}
|
|
16
|
+
let failed=null;this.editGroup(()=>{for(const[column,value]of values){const spec=this.edit.specs.get(column);if(!spec){failed=new Error("CanopyX: the calendar column "+column+" is not editable.");return}
|
|
17
|
+
const previous=cellValue(record?.cells[column]);if(!this.editApply(event.key,spec,value,"calendar",record)){failed=false;return}
|
|
18
|
+
applied.push([spec,previous])}});if(failed!==null){if(failed)this.emit("error",{error:failed});if(applied.length)this.edit.undo.pop();for(const[spec,previous]of applied.reverse())this.editApply(event.key,spec,previous,"undo",record);return false}
|
|
19
|
+
this.calVersion++;const time=this.calFmt.shortDay(this.calZone.day(proposal.start))+ " " +(proposal.allDay?"":this.calFmt.time(proposal.start));this.calAnnounce(event.recurring?fill(labels.calendarSeriesMoved,{title:event.title}):delta?fill(labels.calendarMoved,{title:event.title,time}):fill(labels.calendarResized,{title:event.title,time:this.calFmt.time(proposal.end)}));if(cal.editable.commit==="manual"){this.calReload({keep:true});return true}
|
|
20
|
+
return this.calCommit([event.key],event.title)},async calCommit(ids,title,keep){try{const result=await this.commitChanges({ids});if(result&&result.failed){const row=(result.rows||[]).find(item=>item.status!=="saved");throw new Error(row?.error||(row?.status==="conflict"?this.edit.labels.gridEditConflict:this.edit.labels.gridEditNotSaved))}
|
|
21
|
+
this.calNotice("",false);return true}catch(error){if(!keep)this.revertChanges(ids);const text=fill(this.calLabels.calendarSaveFailed,{title:title||"",error:error.message});this.calNotice(text,true,true);this.calAnnounce(text);return false}finally{this.calVersion++;this.calCache?.clear();this.calReload({keep:true})}}});Object.assign(DataGrid.prototype,{calOverridden(event){if(event.base.overridden)return true;if(!this.cal.series)return false;if(this.view.source.kind!=="inline"||this.opts.source)return null;const rows=this.view.rows,column=this.cal.series.id;if(this.calSeriesIndex?.rows!==rows||this.calSeriesIndex.version!==this.calVersion){const ids=new Set();for(const row of rows){const value=cellValue(row.cells[column]);if(value!=null&&value!=="")ids.add(String(value))}
|
|
22
|
+
this.calSeriesIndex={rows,version:this.calVersion,ids}}
|
|
23
|
+
return this.calSeriesIndex.ids.has(String(event.base.key))}});function calWrapEditRecord(){const current=DataGrid.prototype.editRecord;if(!current||current._calendar)return;DataGrid.prototype.editRecord=function(id){const found=current.call(this,id);if(found||!this.cal)return found;const event=this.calEvents.find(item=>item.key===id);return event?JSON.parse(JSON.stringify(event.base.row)):found};DataGrid.prototype.editRecord._calendar=true}
|
|
24
|
+
const CAL_DRAG_START=4,CAL_EDGE=6;Object.assign(DataGrid.prototype,{calDragStart(pointer){const element=pointer.target.closest(".cx-cal-ev"),event=element?._calEvent;if(!element){this.calRangeStart(pointer);return}
|
|
25
|
+
if(!event||pointer.button!==0||!this.calCanEdit(event,false)&&!this.calCanEdit(event,true))return;const box=element.getBoundingClientRect(),block=element.classList.contains("cx-cal-ev-block"),lane=element.classList.contains("cx-cal-ev-lane");const vertical=block,rtl=this.calRtl,edge=vertical?box.bottom-pointer.clientY:rtl?pointer.clientX-box.left:box.right-pointer.clientX,top=vertical?pointer.clientY-box.top:rtl?box.right-pointer.clientX:pointer.clientX-box.left;const mode=edge<=CAL_EDGE&&this.calCanEdit(event,true)&&!event.marker?"end":top<=CAL_EDGE&&(vertical||lane)&&this.calCanEdit(event,true)&&!event.marker?"start":this.calCanEdit(event,false)?"move":null;if(!mode)return;const origin=this.calPointAt(pointer.clientX,pointer.clientY);if(!origin)return;this.calDrag={event,element,mode,origin,x:pointer.clientX,y:pointer.clientY,id:pointer.pointerId,started:false,proposal:null};element.setPointerCapture?.(pointer.pointerId)},calDragMove(pointer){if(this.calCreateDrag){this.calRangeMove(pointer);return}
|
|
26
|
+
const drag=this.calDrag;if(!drag||pointer.pointerId!==drag.id)return;if(drag.cancelled){pointer.preventDefault();return}
|
|
27
|
+
if(!drag.started&&Math.hypot(pointer.clientX-drag.x,pointer.clientY-drag.y)<CAL_DRAG_START)return;if(!drag.started){drag.started=true;drag.element.classList.add("cx-cal-dragging");this.host.classList.add("cx-cal-drag")}
|
|
28
|
+
pointer.preventDefault();drag.lastX=pointer.clientX;drag.lastY=pointer.clientY;if(drag.frame)return;drag.frame=requestAnimationFrame(()=>{drag.frame=0;const point=this.calPointAt(drag.lastX,drag.lastY);if(point)this.calPropose(drag,point)})},async calDragEnd(pointer,cancel){if(this.calCreateDrag){this.calRangeEnd(pointer,cancel);return}
|
|
29
|
+
const drag=this.calDrag;if(!drag||pointer&&pointer.pointerId!==drag.id)return;if(cancel)this.calCancelGesture();this.calDrag=null;cancelAnimationFrame(drag.frame);drag.element.classList.remove("cx-cal-dragging");this.host.classList.remove("cx-cal-drag");this.calGhost?.remove();this.calGhost=null;if(!drag.started&&!drag.cancelled)return;this.calSuppressClick=true;setTimeout(()=>{this.calSuppressClick=false},0);if(cancel||drag.cancelled||!drag.proposal)return;if(drag.problem){this.calAnnounce(drag.problem);return}
|
|
30
|
+
await this.calApply(drag.event,drag.proposal).catch(error=>this.emit("error",{error}))},calCancelGesture(){const drag=this.calDrag,range=this.calCreateDrag;if(!drag&&!range)return false;if(drag){drag.cancelled=true;cancelAnimationFrame(drag.frame);drag.frame=0;drag.element.classList.remove("cx-cal-dragging");this.host.classList.remove("cx-cal-drag")}
|
|
31
|
+
if(range&&!range.cancelled){range.cancelled=true;this.calSelection=range.selection;this.calMarkSelection()}
|
|
32
|
+
this.calGhost?.remove();this.calGhost=null;return true},calPointAt(x,y){if(this.calView==="timeline")return this.calTimelinePoint?.(x,y)||null;const target=document.elementFromPoint(x,y)?.closest?.("[data-cx-day]");if(calDayGrid(this.calView)||!this.calScroller)return target?.dataset.cxDay?{day:Number(target.dataset.cxDay),minutes:null,resource:target.dataset.cxResource??null}:null;const slots=this.calScroller.firstChild,box=slots.getBoundingClientRect(),cal=this.cal;if(target?.classList.contains("cx-cal-allcell"))return{day:Number(target.dataset.cxDay),minutes:null,resource:target.dataset.cxResource??null,allDayRow:true};const columns=[...this.calGrid.querySelectorAll(".cx-cal-allcell")],width=(box.width-CAL_GUTTER)/ columns.length;
|
|
33
|
+
const index=Math.max(0,Math.min(columns.length-1,Math.floor(((this.calRtl?box.right-x:x-box.left)- CAL_GUTTER)/ width)));
|
|
34
|
+
const minutes=Math.max(cal.minMinutes,Math.min(cal.maxMinutes,cal.minMinutes +(y-box.top)/ (CAL_SLOT_PX / cal.slotMinutes)));
|
|
35
|
+
return{day:Number(columns[index].dataset.cxDay),minutes,resource:columns[index].dataset.cxResource??null,column:index}},calPropose(drag,point){const event=drag.event,zone=event.allDay?calZone("utc"):this.calZone,snap=this.calView==="timeline"?this.tlSnap():this.cal.editable?.snap??this.cal.slotMinutes,origin=drag.origin;const snapped=minutes=>Math.round(minutes / snap) * snap;
|
|
36
|
+
let start=event.start,end=event.end;const timed=point.minutes!=null&&origin.minutes!=null&&!event.allDay;if(drag.mode==="move"){if(timed){const offset=snapped(point.minutes-origin.minutes),startParts=zone.parts(event.start);start=zone.instant(startParts.day +(point.day-origin.day),startParts.minutes+offset)}else{const startParts=zone.parts(event.start);start=zone.instant(startParts.day +(point.day-origin.day),startParts.minutes)}
|
|
37
|
+
end=start +(event.end-event.start)}else if(drag.mode==="end"){end=timed?zone.instant(point.day,snapped(point.minutes)):event.allDay?zone.start(point.day+1):zone.instant(point.day,zone.parts(event.end).minutes);end=Math.max(end,start +(event.allDay?CAL_DAY:snap*CAL_MINUTE))}else{start=timed?zone.instant(point.day,snapped(point.minutes)):event.allDay?zone.start(point.day):zone.instant(point.day,zone.parts(event.start).minutes);start=Math.min(start,end -(event.allDay?CAL_DAY:snap*CAL_MINUTE))}
|
|
38
|
+
const resource=drag.mode==="move"&&this.cal.editable.resources&&point.resource!=null?point.resource:event.resource;drag.proposal={start,end,resource,allDay:event.allDay};drag.problem=this.calCheck(event,drag.proposal);this.calDrawGhost(drag)},calDrawGhost(drag){const proposal=drag.proposal,event=drag.event;if(this.calView==="timeline"){this.calTimelineGhost?.(drag);return}
|
|
39
|
+
const ghost=this.calGhost||=h("div","cx-cal-ghost");ghost.setAttribute("aria-hidden","true");ghost.classList.toggle("cx-cal-conflict",!!drag.problem);const label=(drag.drop?event.title+" · ":"")+ this.calTimeText({...event,start:proposal.start,end:proposal.end,sd:this.calZone.day(proposal.start),ed:this.calZone.day(Math.max(proposal.start,proposal.end-1))});setText(ghost,drag.problem?label+" · "+drag.problem:label);ghost.title=ghost.textContent;const sd=event.allDay?Math.floor(proposal.start / CAL_DAY) : this.calZone.day(proposal.start);
|
|
40
|
+
const cell=this.calBody.querySelector('.cx-cal-day[data-cx-day="'+sd+'"],.cx-cal-allcell[data-cx-day="'+sd+'"]' +(proposal.resource!=null&&!calDayGrid(this.calView)?'[data-cx-resource="'+CSS.escape(proposal.resource)+ '"]':""));if(!calDayGrid(this.calView)&&!calInAllDayRow(event)&&this.calScroller){const slots=this.calScroller.firstChild,columns=[...this.calGrid.querySelectorAll(".cx-cal-allcell")];const index=columns.findIndex(item=>Number(item.dataset.cxDay)===sd&&(proposal.resource==null||item.dataset.cxResource===undefined||item.dataset.cxResource===proposal.resource));if(index<0){ghost.remove();return}
|
|
41
|
+
const top=this.calZone.parts(proposal.start).minutes-this.cal.minMinutes,length=Math.max(this.calMinSegment,(proposal.end-proposal.start)/ CAL_MINUTE);
|
|
42
|
+
ghost.className="cx-cal-ghost cx-cal-ghost-block" +(drag.problem?" cx-cal-conflict":"");ghost.style.setProperty("--d",String(Math.max(0,index)));ghost.style.setProperty("--t",String(top));ghost.style.setProperty("--h",String(length));if(ghost.parentNode!==slots)slots.appendChild(ghost)}else if(cell){const days=Math.max(1,Math.round((proposal.end-proposal.start)/ CAL_DAY));
|
|
43
|
+
ghost.className="cx-cal-ghost cx-cal-ghost-bar" +(drag.problem?" cx-cal-conflict":"");ghost.style.setProperty("--s",String(days));ghost.style.setProperty("--c",String([...cell.parentNode.children].filter(item=>item.matches(".cx-cal-day,.cx-cal-allcell")).indexOf(cell)));if(ghost.parentNode!==cell)cell.appendChild(ghost)}},calEditKey(key){if(key.key==="Escape"){const gesture=this.calCancelGesture(),drop=this.calCancelDrop?.();if(gesture||drop){this.calStopMoving();key.preventDefault();return true}}
|
|
44
|
+
const target=key.target.closest?.(".cx-cal-ev"),moving=this.calMoving;if(!moving){if(key.key!=="F2"||!target?._calEvent||!this.calCanEdit(target._calEvent,false)&&!this.calCanEdit(target._calEvent,true))return false;key.preventDefault();return this.calStartMove(target)}
|
|
45
|
+
return this.calMoveKey(key,moving)},calStartMove(target){{const event=target._calEvent;if(!event||!this.calCanEdit(event,false)&&!this.calCanEdit(event,true))return false;this.calMoving={event,element:target,mode:"move",origin:{day:event.sd,minutes:0},proposal:{start:event.start,end:event.end,resource:event.resource,allDay:event.allDay}};target.classList.add("cx-cal-dragging");this.calAnnounce(this.calLabels.calendarMoveHelp);this.calMoving.blur=()=>this.calStopMoving();target.addEventListener("blur",this.calMoving.blur);target.focus({preventScroll:true});return true}},calMoveKey(key,moving){const event=moving.event,proposal=moving.proposal,timeView=!calDayGrid(this.calView)&&this.calView!=="timeline"&&!calInAllDayRow(event);const zone=event.allDay?calZone("utc"):this.calZone,step=this.cal.editable.snap*CAL_MINUTE;const days=(time,count)=>zone.instant(zone.day(time)+ count,zone.parts(time).minutes +(time%CAL_MINUTE)/ CAL_MINUTE);
|
|
46
|
+
let start=proposal.start,end=proposal.end,resource=proposal.resource;if(key.key==="Escape"){key.preventDefault();this.calStopMoving();return true}
|
|
47
|
+
if(key.key==="Enter"){key.preventDefault();const proposalNow=moving.proposal,problem=moving.problem;this.calStopMoving();if(problem)this.calAnnounce(problem);else this.calApply(event,proposalNow).catch(error=>this.emit("error",{error}));return true}
|
|
48
|
+
const vertical=key.key==="ArrowUp"||key.key==="ArrowDown",horizontal=key.key==="ArrowLeft"||key.key==="ArrowRight";if(["Shift","Alt","Control","Meta"].includes(key.key))return true;if(!vertical&&!horizontal){this.calStopMoving();return false}
|
|
49
|
+
key.preventDefault();const sign=key.key==="ArrowUp"||key.key==="ArrowLeft"?-1:1;if(key.altKey&&vertical&&this.cal.editable.resources&&this.calResourceStep)resource=this.calResourceStep(resource,sign);else if(key.shiftKey&&this.calCanEdit(event,true))end=Math.max(start +(event.allDay?CAL_DAY:step),timeView&&vertical?end+sign*step:days(end,sign));else if(timeView&&vertical){start +=sign*step;end +=sign*step}
|
|
50
|
+
else{const count=sign*(vertical?7:1),length=end-start;start=days(start,count);end=event.allDay?days(end,count):start+length}
|
|
51
|
+
moving.proposal={start,end,resource,allDay:event.allDay};moving.problem=this.calCheck(event,moving.proposal);this.calDrawGhost(moving);const named=resource!=null&&resource!==event.resource&&this.calResourceName?", "+this.calResourceName(resource):"";this.calAnnounce(this.calTimeText({...event,start,end,sd:this.calZone.day(start),ed:this.calZone.day(Math.max(start,end-1))})+ named +(moving.problem?", "+moving.problem:""));return true},calStopMoving(){const moving=this.calMoving;if(!moving)return;this.calMoving=null;moving.element.classList.remove("cx-cal-dragging");moving.element.removeEventListener("blur",moving.blur);this.calGhost?.remove();this.calGhost=null}});Object.assign(DataGrid.prototype,{calCreateProposal(cell,last){const cal=this.cal,zone=this.calZone,day=Number(cell.dataset.cxDay),resource=this.calCellResource(cell);if(cell.dataset.cxMinutes!==undefined){const from=Number(cell.dataset.cxMinutes),to=last?.dataset.cxMinutes!==undefined?Number(last.dataset.cxMinutes):from;const first=Math.min(from,to),end=last?Math.max(from,to)+ cal.slotMinutes:first+cal.create.minutes;return{start:zone.instant(day,first),end:zone.instant(day,Math.min(end,1440)),allDay:false,resource}}
|
|
52
|
+
const other=last?Number(last.dataset.cxDay):day,a=Math.min(day,other),b=Math.max(day,other);if(cal.create.allDay)return{start:a*CAL_DAY,end:(b+1)*CAL_DAY,allDay:true,resource};const at=cal.working?.start??540;return{start:zone.instant(a,at),end:zone.instant(b,at+cal.create.minutes),allDay:false,resource}},calCreateProblem(proposal){const labels=this.calLabels;if(!this.calAllowsSpan(proposal.start,proposal.end,proposal.allDay))return labels.calendarOutsideRange;for(const range of this.calRanges)if(range.kind==="unavailable"&&(range.resource==null||range.resource===proposal.resource)&&calOverlaps(proposal.start,proposal.end,range.start,range.end))return labels.calendarUnavailable;return null},async calCreate(proposal,anchor){if(!this.cal.create||this.editReadOnly)return false;const problem=this.calCreateProblem(proposal);if(problem){this.calAnnounce(problem);this.calNotice(problem,true,true);return false}
|
|
53
|
+
const detail={start:calIso(proposal.start),end:calIso(proposal.end),allDay:proposal.allDay,resource:proposal.resource};let go=this.emit("eventCreate",detail);if(go&&typeof this.opts.onCreate==="function"){try{go=this.opts.onCreate(gridFreezeCopy(detail))!==false}catch(error){this.emit("error",{error});go=false}}
|
|
54
|
+
if(!go)return false;this.calCreateForm(proposal,anchor);return true},calCreateForm(proposal,anchor){this.calCloseCreate(false);const labels=this.calLabels,panel=this.calCreating=h("form","cx-cal-create"),titleId=this.id+"-cal-new";const heading=h("h3","cx-cal-create-h"),when=h("p","cx-cal-det-when"),label=h("label","cx-cal-create-l"),input=h("input","cx-cal-create-input");const add=h("button","cx-btn cx-cal-create-add"),cancel=h("button","cx-btn ghost cx-cal-create-cancel"),actions=h("div","cx-cal-create-actions");panel.setAttribute("role","dialog");panel.setAttribute("aria-modal","false");panel.setAttribute("aria-labelledby",titleId);heading.id=titleId;setText(heading,labels.calendarNewEvent);setText(when,this.calTimeText({start:proposal.start,end:proposal.end,allDay:proposal.allDay,sd:proposal.allDay?Math.floor(proposal.start / CAL_DAY) : this.calZone.day(proposal.start),
|
|
55
|
+
ed:proposal.allDay?Math.floor((proposal.end-1)/ CAL_DAY) : this.calZone.day(Math.max(proposal.start, proposal.end - 1)), marker: false }));
|
|
56
|
+
input.id=titleId+"-title";input.type="text";input.maxLength=1000;input.autocomplete="off";label.htmlFor=input.id;setText(label,labels.calendarEventTitle);add.type="submit";setText(add,labels.calendarCreate);cancel.type="button";setText(cancel,labels.calendarCancel);actions.append(cancel,add);panel.append(heading,when,label,input,actions);panel._calAnchor=anchor;this.calPlace(panel,anchor||this.calBody);cancel.addEventListener("click",()=>this.calCloseCreate(true));panel.addEventListener("keydown",key=>{if(key.key==="Escape"){key.preventDefault();key.stopPropagation();this.calCloseCreate(true)}});panel.addEventListener("submit",submit=>{submit.preventDefault();const title=input.value.trim();if(!title){input.focus();return}
|
|
57
|
+
this.calCloseCreate(true);this.calInsertEvent(proposal,title).catch(error=>this.emit("error",{error}))});this.calCreateOutside=press=>{if(!panel.contains(press.target))this.calCloseCreate(false)};document.addEventListener("pointerdown",this.calCreateOutside,true);input.focus({preventScroll:true})},calCloseCreate(restore){const panel=this.calCreating;if(!panel)return;this.calCreating=null;document.removeEventListener("pointerdown",this.calCreateOutside,true);panel.remove();if(restore&&panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true})},async calInsertEvent(proposal,title,extra){const cal=this.cal,cells={[cal.title.id]:title,[cal.start.id]:calIso(proposal.start),...extra};if(cal.end)cells[cal.end.id]=calIso(proposal.end);if(cal.allDay?.editable&&proposal.allDay)cells[cal.allDay.id]=true;if(cal.resource?.editable&&proposal.resource!=null)cells[cal.resource.id]=proposal.resource;await this.loadEditing();const result=await this.insertRow({cells});if(!result.applied)return false;const time=this.calFmt.shortDay(proposal.allDay?Math.floor(proposal.start / CAL_DAY) : this.calZone.day(proposal.start)) + (proposal.allDay ? "" : " " + this.calFmt.time(proposal.start));
|
|
58
|
+
this.calAnnounce(fill(this.calLabels.calendarCreated,{title,time}));this.calVersion++;if(!result.valid){this.calInvalidInsert(result);return true}
|
|
59
|
+
if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
|
|
60
|
+
return this.calCommit([result.id],title)},calCreateFromClick(cell){return this.calCreate(this.calCreateProposal(cell),cell)},calCreateFromKey(event,cell){if(!this.cal.create||!cell||this.calMoving)return false;const empty=!cell.querySelector(".cx-cal-ev");if(!(event.key==="n"||event.key==="N"||event.key==="Enter"&&empty)||event.ctrlKey||event.metaKey||event.altKey)return false;event.preventDefault();this.calCreate(this.calCreateProposal(cell),cell).catch(error=>this.emit("error",{error}));return true},calRangeStart(pointer){if(!this.cal.create||pointer.button!==0||this.editReadOnly)return false;const cell=pointer.target.closest?.("[data-cx-day]");if(!cell||pointer.target.closest(".cx-cal-ev,.cx-cal-more-btn,.cx-cal-num")||cell.classList.contains("cx-cal-invalid"))return false;this.calCreateDrag={first:cell,last:cell,id:pointer.pointerId,x:pointer.clientX,y:pointer.clientY,started:false,selection:this.calSelection};return true},calRangeMove(pointer){const range=this.calCreateDrag;if(!range||pointer.pointerId!==range.id)return false;if(range.cancelled){pointer.preventDefault();return true}
|
|
61
|
+
if(!range.started&&Math.hypot(pointer.clientX-range.x,pointer.clientY-range.y)<6)return true;range.started=true;const cell=document.elementFromPoint(pointer.clientX,pointer.clientY)?.closest?.("[data-cx-day]");if(cell&&this.calBody.contains(cell)&&(range.first.dataset.cxMinutes===undefined)===(cell.dataset.cxMinutes===undefined)&&
|
|
62
|
+
(cell.dataset.cxMinutes===undefined||cell.dataset.cxDay===range.first.dataset.cxDay&&cell.dataset.cxResource===range.first.dataset.cxResource)){range.last=cell;const proposal=this.calCreateProposal(range.first,cell);if(cell.dataset.cxMinutes!==undefined)this.calSelectSlotsQuiet(Number(cell.dataset.cxDay),proposal,cell.dataset.cxResource);else this.calSelectDaysQuiet(Math.min(Number(range.first.dataset.cxDay),Number(cell.dataset.cxDay)),Math.max(Number(range.first.dataset.cxDay),Number(cell.dataset.cxDay)),this.calCellResource(range.first))}
|
|
63
|
+
pointer.preventDefault();return true},calRangeEnd(pointer,cancel){const range=this.calCreateDrag;if(!range||pointer&&pointer.pointerId!==range.id)return false;if(cancel)this.calCancelGesture();this.calCreateDrag=null;if(!range.started&&!range.cancelled)return true;this.calSuppressClick=true;setTimeout(()=>{this.calSuppressClick=false},0);if(cancel||range.cancelled)return true;this.calCreate(this.calCreateProposal(range.first,range.last),range.last).catch(error=>this.emit("error",{error}));return true},calSelectSlotsQuiet(day,proposal,resource){const zone=this.calZone;if(!this.calAllowsSpan(proposal.start,proposal.end,proposal.allDay))return;this.calSelection={first:day,last:day,from:proposal.start,to:proposal.end,fromMinutes:zone.parts(proposal.start).minutes,toMinutes:zone.parts(proposal.end).minutes||1440,resource:resource??null};this.calMarkSelection()},calSelectDaysQuiet(first,last,resource){const zone=this.calZone;if(!this.calAllowsSpan(first*CAL_DAY,(last+1)*CAL_DAY,true))return;this.calSelection={first,last,from:zone.start(first),to:zone.start(last+1),resource:resource??null};this.calMarkSelection()}});const gridFreezeCopy=value=>Object.freeze(JSON.parse(JSON.stringify(value)));const CAL_DROP_TYPES=["application/x-canopyx-row","application/x-canopyx-event"];let calDropElement=null;function calElementItem(element){let data;try{data=JSON.parse(element.getAttribute("data-canopyx-event")||"{}")}catch{return null}
|
|
64
|
+
if(!data||typeof data!=="object"||typeof data.title!=="string"||!data.title.trim()||data.title.length>1000)return null;const minutes=Number.isInteger(data.minutes)&&data.minutes>=5&&data.minutes<=10080?data.minutes:null,cells=Object.create(null);if(data.cells&&typeof data.cells==="object"&&!Array.isArray(data.cells))
|
|
65
|
+
for(const[id,value]of Object.entries(data.cells))if(value===null||["string","number","boolean"].includes(typeof value))cells[id]=value;return Object.freeze({source:"element",id:typeof data.id==="string"?data.id:null,title:data.title.trim(),minutes,allDay:data.allDay===true,cells:Object.freeze(cells)})}
|
|
66
|
+
if(typeof document!=="undefined"){document.addEventListener("dragstart",event=>{GRID_DRAG.item=null;const element=event.target.closest?.("[data-canopyx-event]");calDropElement=element&&element.getAttribute("draggable")==="true"?calElementItem(element):null;if(calDropElement){event.dataTransfer.setData("application/x-canopyx-event",JSON.stringify(calDropElement));event.dataTransfer.setData("text/plain",calDropElement.title);event.dataTransfer.effectAllowed="copyMove"}},true);document.addEventListener("dragend",()=>{calDropElement=null},true)}
|
|
67
|
+
Object.assign(DataGrid.prototype,{calEditStart(){if(!this.cal.drop)return;const body=this.calBody,signal=this.lifetime.signal;body.addEventListener("dragover",event=>this.calDropOver(event),{signal});body.addEventListener("dragleave",event=>{if(!body.contains(event.relatedTarget))this.calDropClear()},{signal});body.addEventListener("drop",event=>this.calDropOn(event),{signal});document.addEventListener("dragend",()=>{if(this.calDropDrag)this.calDropClear()},{signal})},calDropItem(picked){return picked?GRID_DRAG.picked:GRID_DRAG.item||calDropElement},calDropProposal(item,point){if(!point)return null;const cal=this.cal,zone=this.calZone,minutes=item.minutes??cal.create?.minutes??60,resource=cal.resource?.editable?point.resource??null:null,row=point.row??null;if(point.minutes==null||item.allDay){if(cal.allDay?.editable)return{start:point.day*CAL_DAY,end:(point.day+1)*CAL_DAY,allDay:true,resource,row};if(point.minutes==null){const at=cal.working?.start??540;return{start:zone.instant(point.day,at),end:zone.instant(point.day,at+minutes),allDay:false,resource,row}}}
|
|
68
|
+
const snap=this.calView==="timeline"?this.tlSnap():cal.editable?.snap??cal.slotMinutes,at=Math.floor(point.minutes / snap) * snap;
|
|
69
|
+
return{start:zone.instant(point.day,at),end:zone.instant(point.day,at+minutes),allDay:false,resource,row}},calDropOver(event){const types=[...event.dataTransfer.types];if(!CAL_DROP_TYPES.some(type=>types.includes(type)))return;const item=this.calDropItem(false);if(!item||this.editReadOnly)return;event.preventDefault();event.dataTransfer.dropEffect="copy";const proposal=this.calDropProposal(item,this.calPointAt(event.clientX,event.clientY));if(!proposal){this.calDropClear();return}
|
|
70
|
+
const drag=this.calDropDrag={drop:true,event:{title:item.title,allDay:proposal.allDay,start:proposal.start,end:proposal.end,resource:proposal.resource,marker:false},proposal,problem:this.calCreateProblem(proposal)};this.calDrawGhost(drag)},calDropClear(){this.calDropDrag=null;this.calGhost?.remove();this.calGhost=null},calCancelDrop(){if(!this.cal.drop||!this.calDropDrag&&!GRID_DRAG.picked)return false;GRID_DRAG.picked=null;if(this.calDropDrag){GRID_DRAG.item=null;calDropElement=null;this.calDropClear()}
|
|
71
|
+
return true},calDropOn(event){const item=this.calDropItem(false);const proposal=item&&this.calDropProposal(item,this.calPointAt(event.clientX,event.clientY));this.calDropClear();if(!item||!proposal)return;event.preventDefault();GRID_DRAG.item=null;calDropElement=null;this.calReceive(item,proposal).catch(error=>this.emit("error",{error}))},calDropFromKey(event,cell){if(event.key==="Escape"&&this.calCancelDrop()){event.preventDefault();return true}
|
|
72
|
+
if(!this.cal.drop||event.key!=="Enter"||!GRID_DRAG.picked||!cell||this.editReadOnly)return false;event.preventDefault();const item=GRID_DRAG.picked,point={day:Number(cell.dataset.cxDay),minutes:cell.dataset.cxMinutes!==undefined?Number(cell.dataset.cxMinutes):null,resource:this.calCellResource(cell)};GRID_DRAG.picked=null;this.calReceive(item,this.calDropProposal(item,point)).catch(error=>this.emit("error",{error}));return true},async calReceive(item,proposal){if(this.editReadOnly)return false;const problem=this.calCreateProblem(proposal);if(problem){this.calAnnounce(problem);this.calNotice(problem,true,true);return false}
|
|
73
|
+
const detail={item,start:calIso(proposal.start),end:calIso(proposal.end),allDay:proposal.allDay,resource:proposal.resource};let go=this.emit("eventReceive",detail);if(go&&typeof this.opts.onReceive==="function"){try{go=this.opts.onReceive(detail)!==false}catch(error){this.emit("error",{error});go=false}}
|
|
74
|
+
if(!go)return false;const cal=this.cal,own=new Set(this.columns.map(column=>column.id)),skip=new Set([cal.start.id,cal.end?.id,cal.allDay?.id,cal.title.id,cal.recurrence?.id,cal.exceptions?.id,cal.series?.id,cal.recurrenceId?.id,cal.seriesEnd?.id].filter(Boolean)),extra={};for(const[id,value]of Object.entries(item.cells||{}))if(own.has(id)&&!skip.has(id))extra[id]=value;const done=await this.calInsertEvent(proposal,item.title,extra);if(done){const time=this.calFmt.shortDay(proposal.allDay?Math.floor(proposal.start / CAL_DAY) : this.calZone.day(proposal.start)) + (proposal.allDay ? "" : " " + this.calFmt.time(proposal.start));
|
|
75
|
+
this.calAnnounce(fill(this.calLabels.calendarReceived,{title:item.title,time}))}
|
|
76
|
+
return done}});const CAL_RULE_SPLIT=/;\s*/;
|
|
77
|
+
function calRuleParts(text){return String(text).trim().replace(/^RRULE:/i, "").split(CAL_RULE_SPLIT).filter(Boolean).map(part => { const at = part.indexOf("="); return [part.slice(0, at).trim().toUpperCase(), part.slice(at + 1).trim()]; });
|
|
78
|
+
}
|
|
79
|
+
const calRuleText=parts=>parts.map(([name,value])=>name+"="+value).join(";");function calUntilText(time,allDay){const iso=new Date(time).toISOString();return allDay?iso.slice(0,10).replace(/-/g, "") : iso.slice(0, 19).replace(/[-:]/g, "") + "Z";
|
|
80
|
+
}
|
|
81
|
+
const calMoveTime=(time,zone,minutes)=>minutes?zone.instant(zone.day(time),zone.parts(time).minutes+minutes)+(time%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE:time;function calMovedRule(text,base,zone,start){const minutes=zone.parts(start).minutes-zone.parts(base.start).minutes;const until=base.rule.until;if(!minutes||!until||until.time===undefined&&until.minutes===undefined)return text;const time=until.time??zone.instant(until.day,until.minutes);let shifted=calMoveTime(time,zone,minutes);const boundary=DataGrid.prototype.calKit.calExpand(base.rule,base.start,0,zone,zone.start(zone.day(time)- 1),time+1,new Set(),new Set(),8);if(boundary.length===8)return null;const last=boundary.at(-1);if(last!==undefined){const rec=DataGrid.prototype.calKit,open=rec.calRule(calRuleText(calRuleParts(text).filter(([name])=>name!=="UNTIL")));const candidates=rec.calExpand(open,start,0,zone,zone.start(zone.day(last)- 1),zone.start(zone.day(last)+ 1),new Set(),new Set(),8);const moved=candidates.find(time=>zone.day(time)===zone.day(last));if(candidates.length===8||moved===undefined)return null;shifted=Math.max(shifted,Math.ceil(moved / 1000) * 1000);
|
|
82
|
+
}
|
|
83
|
+
const value=until.time!==undefined?calUntilText(shifted,false):
|
|
84
|
+
calUntilText(zone.day(shifted)*CAL_DAY+zone.parts(shifted).minutes*CAL_MINUTE +(shifted%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE,false).slice(0,-1);return calRuleText(calRuleParts(text).map(([name,valueNow])=>[name,name==="UNTIL"?value:valueNow]))}
|
|
85
|
+
Object.assign(DataGrid.prototype,{calEditSnapshot(event){const columns=[this.cal.start,this.cal.end,this.cal.allDay,this.cal.resource,this.cal.recurrence,this.cal.exceptions,this.cal.series,this.cal.recurrenceId].filter(Boolean);return{cal:this.cal,view:this.view,source:this.source,edit:this.edit,options:this.edit?.options,writer:this.edit?.options.commit,sourceWriter:this.source.commit,cells:columns.map(column=>[column.id,cellValue(event.base.row.cells[column.id])??null])}},calEditCurrent(event,decision){if(this.destroyed||!this.calBody?.isConnected||!this.calAnswer||this.calAnswer.source!==decision.source||this.cal!==decision.cal||this.view!==decision.view||
|
|
86
|
+
this.source!==decision.source||this.edit!==decision.edit||this.edit?.options!==decision.options||
|
|
87
|
+
this.edit?.options.commit!==decision.writer||this.source.commit!==decision.sourceWriter)return null;const current=this.calEvents.find(item=>item.id===event.id&&item.key===event.key&&item.start===event.start&&item.end===event.end);if(!current)return null;const record=this.editRecord(event.key)||current.base.row;if(decision.cells.some(([id,value])=>(cellValue(current.base.row.cells[id])??null)!==value||(cellValue(record.cells[id])??null)!==value))return null;return{...current,row:record,base:{...current.base,row:record}}},calRefuseDecision(){if(!this.destroyed&&this.calBody?.isConnected){this.calAnnounce(this.calLabels.calendarUnavailable);this.calNotice(this.calLabels.calendarUnavailable,true,true)}
|
|
88
|
+
return false},calOccurrencePlan(event,proposal,mode){const cal=this.cal,base=event.base,zone=base.seriesZone,rec=this.calKit;const refused=()=>({problem:this.calLabels.calendarSeriesTime});if(mode==="following"&&event.start===base.start)mode="all";const resource=proposal.resource!==undefined?proposal.resource:event.resource,cells=this.calSeriesValues(event);cells[cal.start.id]=calIso(proposal.start);if(cal.end)cells[cal.end.id]=calIso(proposal.end);if(cal.resource&&proposal.resource!==undefined&&(mode!=="all"||proposal.resource!==event.resource))cells[cal.resource.id]=proposal.resource;if(mode==="this"){if(!cal.series||!cal.recurrenceId)return refused();cells[cal.series.id]=event.key;cells[cal.recurrenceId.id]=calIso(event.start);return{mode,cells,spans:[{...proposal,resource}]}}
|
|
89
|
+
if(zone.day(proposal.start)!==zone.day(event.start)||this.calOverridden(event)!==false)return refused();const minutes=zone.parts(proposal.start).minutes-zone.parts(event.start).minutes;const start=mode==="all"?calMoveTime(base.start,zone,minutes):proposal.start;const duration=mode==="all"?base.end-base.start+proposal.end-proposal.start -(event.end-event.start):proposal.end-proposal.start;const text=String(cellValue(base.row.cells[cal.recurrence.id])??""),parts=calRuleParts(text);let ruleText=text,oldRule=null;if(mode==="following"){const count=parts.find(([name])=>name==="COUNT"),kept=parts.filter(([name])=>name!=="COUNT"&&name!=="UNTIL");const before=count?rec.calExpand(base.rule,base.start,0,zone,base.start,event.start,new Set(),new Set(),10000).length:0;oldRule=calRuleText(count?[...kept,["COUNT",String(Math.max(1,before))]]:[...kept,["UNTIL",calUntilText(event.start-1000,base.allDay)]]);ruleText=calRuleText(count?[...kept,["COUNT",String(Math.max(1,Number(count[1])- before))]]:parts)}
|
|
90
|
+
ruleText=calMovedRule(ruleText,base,zone,start);if(ruleText===null)return refused();const rule=rec.calRule(ruleText),excluded=[...base.excluded].filter(time=>mode==="all"||time>=event.start);const excludedDays=new Set([...base.excludedDays].filter(day=>mode==="all"||day>=zone.day(event.start)));const from=base.allDay?this.calRange.firstDay*CAL_DAY:this.calZone.start(this.calRange.firstDay);const to=base.allDay?(this.calRange.lastDay+1)*CAL_DAY:this.calZone.start(this.calRange.lastDay+1);const loadedDays=new Set(this.calEvents.filter(other=>other.key===event.key).map(other=>zone.day(other.start)));let first=from,last=to;for(const day of[...loadedDays,...excluded.map(time=>zone.day(time)),zone.day(event.start)]){first=Math.min(first,zone.start(day));last=Math.max(last,zone.start(day+1))}
|
|
91
|
+
const original=rec.calExpand(base.rule,base.start,Math.max(base.end-base.start,duration),zone,first,last,new Set(),new Set(),cal.limit+1);const candidate=rec.calExpand(rule,start,duration,zone,first,last,new Set(),new Set(),cal.limit+1);if(original.length>cal.limit||candidate.length>cal.limit)return refused();const originalStarts=new Set(original),byDay=new Map(candidate.map(time=>[zone.day(time),time])),movedExcluded=new Set();if(byDay.size!==candidate.length)return refused();for(const time of excluded){const moved=byDay.get(zone.day(time));if(!originalStarts.has(time)||moved===undefined)return refused();movedExcluded.add(moved)}
|
|
92
|
+
if(byDay.get(zone.day(event.start))!==proposal.start||proposal.end!==proposal.start+duration)return refused();const sameExcluded=excluded.length===movedExcluded.size&&excluded.every(time=>movedExcluded.has(time));if(mode==="all"&&!sameExcluded&&!this.canEditCell(this.editRecord(event.key),cal.exceptions.id))return refused();cells[cal.start.id]=calIso(start);if(cal.end)cells[cal.end.id]=duration>0||!base.marker?calIso(start+duration):null;cells[cal.recurrence.id]=ruleText;if(cal.exceptions)cells[cal.exceptions.id]=mode==="all"&&sameExcluded?
|
|
93
|
+
cellValue(base.row.cells[cal.exceptions.id]):[...[...excludedDays].map(calDayText),...[...movedExcluded].map(calIso)].join(",")||null;const spans=candidate.filter(time=>!movedExcluded.has(time)&&!excludedDays.has(zone.day(time))&&
|
|
94
|
+
(loadedDays.has(zone.day(time))||calOverlaps(time,time+duration,from,to)))
|
|
95
|
+
.map(time=>({start:time,end:time+duration,allDay:base.allDay,resource}));const values=[[cal.start.id,cells[cal.start.id]]];for(const column of[cal.end,cal.resource,cal.recurrence,cal.exceptions].filter(Boolean))
|
|
96
|
+
if((cells[column.id]??null)!==(cellValue(base.row.cells[column.id])??null))values.push([column.id,cells[column.id]]);return{mode,cells,values,oldRule,spans}},async calOccurrenceMode(event,proposal,decision){let mode=this.cal.occurrence;const hook=this.opts.calendar?.occurrence;if(typeof hook==="function"){let decided;try{decided=await hook(Object.freeze({id:event.key,occurrence:calIso(event.start),start:calIso(proposal.start),end:calIso(proposal.end),resource:proposal.resource??null}))}
|
|
97
|
+
catch(error){this.emit("error",{error});return null}
|
|
98
|
+
if(decided===false)return null;if(["this","following","all"].includes(decided))mode=decided}
|
|
99
|
+
if(decision&&!this.calEditCurrent(event,decision))return null;return mode==="ask"?this.calAskOccurrence(event):mode},calAskOccurrence(event){return new Promise(resolve=>{this.calCloseAsk(null);const labels=this.calLabels,panel=this.calAsk=h("div","cx-cal-ask"),titleId=this.id+"-cal-ask",title=h("h3","cx-cal-create-h"),what=h("p","cx-cal-det-when");panel.setAttribute("role","dialog");panel.setAttribute("aria-modal","false");panel.setAttribute("aria-labelledby",titleId);title.id=titleId;setText(title,labels.calendarOccurrenceTitle);setText(what,event.title+" · "+this.calTimeText(event));const buttons=[["this",labels.calendarThisEvent],["following",labels.calendarThisAndFollowing],["all",labels.calendarAllEvents],[null,labels.calendarCancel]].map(([mode,text])=>{const button=h("button","cx-btn" +(mode?"":" ghost")+ " cx-cal-ask-" +(mode||"cancel"));button.type="button";setText(button,text);button.addEventListener("click",()=>this.calCloseAsk(mode));return button});const actions=h("div","cx-cal-ask-actions");actions.append(...buttons);panel.append(title,what,actions);panel._calResolve=resolve;panel._calReturn=document.activeElement;this.calPlace(panel,this.calBody.querySelector('[data-cx-event="'+CSS.escape(event.id)+ '"]')||this.calBody);panel.addEventListener("keydown",key=>{const at=buttons.indexOf(document.activeElement);if(key.key==="Escape"){key.preventDefault();key.stopPropagation();this.calCloseAsk(null)}
|
|
100
|
+
else if(key.key==="Tab"){key.preventDefault();buttons[(at +(key.shiftKey?buttons.length-1:1))%buttons.length].focus()}
|
|
101
|
+
else if(key.key==="ArrowDown"||key.key==="ArrowRight"){key.preventDefault();buttons[(at+1)%buttons.length].focus()}
|
|
102
|
+
else if(key.key==="ArrowUp"||key.key==="ArrowLeft"){key.preventDefault();buttons[(at+buttons.length-1)%buttons.length].focus()}});buttons[0].focus({preventScroll:true})})},calCloseAsk(mode){const panel=this.calAsk;if(!panel)return;this.calAsk=null;panel.remove();if(panel._calReturn?.isConnected)panel._calReturn.focus({preventScroll:true});panel._calResolve(mode)},calSeriesValues(event){const values=Object.create(null),cal=this.cal,skip=new Set([cal.recurrence?.id,cal.exceptions?.id,cal.seriesEnd?.id,cal.series?.id,cal.recurrenceId?.id].filter(Boolean));for(const[id,cell]of Object.entries(event.base.row.cells))if(!skip.has(id))values[id]=cellValue(cell)??null;return values},calOccurrenceCopyProblem(cells){if(this.editOwnsRows()&&!this.edit.options.commit&&typeof this.source.commit!=="function")return null;return Object.keys(cells).some(id=>cells[id]!=null&&!this.edit.specs.has(id))?this.calLabels.calendarOccurrenceReadOnly:null},async calApplyThis(event,proposal,plan,decision){const cal=this.cal,cells=plan.cells;event=this.calEditCurrent(event,decision);if(!event)return this.calRefuseDecision();const problem=this.calCheck(event,proposal,plan)||this.calOccurrenceCopyProblem(cells);if(problem){this.calAnnounce(problem);this.calNotice(problem,true,true);return false}
|
|
103
|
+
const result=await this.insertRow({cells});if(!result.applied)return false;this.calVersion++;this.calAnnounce(fill(this.calLabels.calendarMoved,{title:event.title,time:this.calFmt.shortDay(this.calZone.day(proposal.start))+(proposal.allDay?"":" "+this.calFmt.time(proposal.start))}));if(!result.valid){this.calInvalidInsert(result);return true}
|
|
104
|
+
if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
|
|
105
|
+
return this.calCommit([result.id],event.title)},async calApplyFollowing(event,proposal,plan,decision){const cal=this.cal,labels=this.calLabels,cells=plan.cells;calWrapEditRecord();event=this.calEditCurrent(event,decision);if(!event)return this.calRefuseDecision();const problem=this.calCheck(event,proposal,plan)||this.calOccurrenceCopyProblem(cells);if(problem){this.calAnnounce(problem);this.calNotice(problem,true,true);return false}
|
|
106
|
+
const spec=this.edit.specs.get(cal.recurrence.id);if(!spec){this.emit("error",{error:new Error("CanopyX: the calendar recurrence column is not editable.")});this.calNotice(labels.calendarSeriesTime,true,true);return false}
|
|
107
|
+
if(!this.canEditCell(this.editRecord(event.key),cal.recurrence.id)){this.calAnnounce(this.edit.labels.gridEditNotSaved);this.calNotice(this.edit.labels.gridEditNotSaved,true,true);return false}
|
|
108
|
+
let inserted=null;const done=this.editGroupIf(()=>{inserted=this.editInsertNow({cells},"calendar");if(!inserted.applied)return false;let cut=false;try{cut=this.editApply(event.key,spec,plan.oldRule,"calendar",this.editRecord(event.key))}
|
|
109
|
+
finally{if(!cut)this.editRemoveInserted(inserted.id)}
|
|
110
|
+
return cut});if(!done)return false;const check=await this.editValidateRow(inserted.id);this.calVersion++;this.calAnnounce(fill(labels.calendarMoved,{title:event.title,time:this.calFmt.shortDay(this.calZone.day(proposal.start))+(proposal.allDay?"":" "+this.calFmt.time(proposal.start))}));if(!check.valid){this.calInvalidInsert({id:inserted.id,errors:check.errors});return true}
|
|
111
|
+
if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
|
|
112
|
+
const history=this.edit.undo.length;if(!await this.calCommit([event.key],event.title)){this.edit.undo.length=Math.max(0,history-1);this.edit.redo.length=0;this.editRemoveInserted(inserted.id,true);this.calReload({keep:true});return false}
|
|
113
|
+
return this.calCommit([inserted.id],event.title,true)},calInvalidInsert(result){const message=Object.values(result.errors||{})[0]||this.edit.labels.gridEditNotSaved;this.calNotice(message,true,true);this.calAnnounce(message);this.calReload({keep:true})}});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["calendar-edit",part])})(typeof window!=="undefined"?window:globalThis);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
(function(global){"use strict";const installed=new WeakSet();const part={version:"feb3ed2cd67d057f",requires:["calendar","calendar-recurrence"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,fill,cellText}=context;const{CAL_DAY,CAL_MINUTE,calBase,calDayNumber,calDayText,calMonthDays,CAL_WEEKDAYS,calZone,calIso,calParseDay,calRule,calExpand}=DataGrid.prototype.calKit;const CAL_ENCODER=new TextEncoder();const CAL_ICS_ZONE_YEARS=100;function calIcsText(value){return String(value??"").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").replace(/\\/g, "\\\\").replace(/;/g, "\\;").replace(/,/g, "\\,").replace(/\r\n|\r|\n/g, "\\n");
|
|
2
|
+
}
|
|
3
|
+
function calIcsFold(line){const out=[];let current="",size=0;for(const char of line){const bytes=CAL_ENCODER.encode(char).length;if(size+bytes>(out.length?74:75)){out.push(current);current="";size=0}
|
|
4
|
+
current +=char;size +=bytes}
|
|
5
|
+
out.push(current);return out.join("\r\n ")}
|
|
6
|
+
function calIcsUtc(time){return new Date(time).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, ""); }
|
|
7
|
+
function calIcsDate(day){return calDayText(day).replace(/-/g, ""); }
|
|
8
|
+
function calIcsWall(zone,time){const p=zone.parts(time),s=Math.floor((time%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE / 1000); return calIcsDate(p.day) + "T" + String(p.h).padStart(2, "0") + String(p.mi).padStart(2, "0") + String(s).padStart(2, "0"); }
|
|
9
|
+
function calIcsMinuteOffset(ms){if(ms%CAL_MINUTE!==0)throw new Error("CanopyX: named-zone iCalendar export needs whole-minute time zone offsets throughout its calendar-year span. Use a supported date range or prepare a separate UTC series.");return ms}
|
|
10
|
+
function calIcsZonedWall(zone,time){const whole=Math.floor(time / 1000) * 1000;
|
|
11
|
+
calIcsMinuteOffset(zone.offset(whole));const parts=zone.parts(whole),remainder=(whole%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE;if(zone.instant(parts.day,parts.minutes)+ remainder!==whole)
|
|
12
|
+
throw new Error("CanopyX: named-zone iCalendar export cannot preserve an instant in the second occurrence of a repeated wall time. Use an unambiguous series time or prepare separate UTC events.");return calIcsWall(zone,whole)}
|
|
13
|
+
function calIcsOffset(ms){const sign=ms<0?"-":"+",minutes=Math.abs(calIcsMinuteOffset(ms))/ CAL_MINUTE; return sign + String(Math.floor(minutes / 60)).padStart(2, "0") + String(minutes % 60).padStart(2, "0"); }
|
|
14
|
+
function calIcsZoneName(zone){const name=zone.name==="local"?Intl.DateTimeFormat().resolvedOptions().timeZone||"UTC":zone.name;const resolved=name==="utc"?"UTC":new Intl.DateTimeFormat("en-US",{timeZone:name}).resolvedOptions().timeZone;return resolved==="Etc/UTC"?"UTC":resolved}
|
|
15
|
+
function calIcsLastYear(base){const rule=base.rule,zone=base.seriesZone;if(rule.count===null&&rule.until===null)
|
|
16
|
+
throw new Error("CanopyX: iCalendar export of a timed series in a named time zone needs COUNT or UNTIL ending within 100 calendar years. Add either limit to its recurrence rule.");const firstYear=new Date(base.start).getUTCFullYear(),bound=Date.UTC(firstYear+CAL_ICS_ZONE_YEARS,0,1),duration=Math.max(0,base.end-base.start);let end;if(rule.until)end=(rule.until.time??(rule.until.minutes===undefined?zone.instant(rule.until.day+1,0)- 1:zone.instant(rule.until.day,rule.until.minutes)))+ duration;else{const starts=calExpand(rule,base.start,0,zone,-Infinity,bound,new Set(),new Set(),rule.count);if(starts.length!==rule.count)throw new Error("CanopyX: zoned iCalendar recurrence export needs a COUNT or UNTIL ending within 100 calendar years.");end=starts[starts.length-1]+ duration}
|
|
17
|
+
if(!Number.isFinite(end)||end>bound)throw new Error("CanopyX: zoned iCalendar recurrence export needs a COUNT or UNTIL ending within 100 calendar years.");return new Date(Math.max(base.start,base.end,end-1)).getUTCFullYear()}
|
|
18
|
+
function calVTimezone(zone,name,year,lastYear){if(lastYear-year>=CAL_ICS_ZONE_YEARS)throw new Error("CanopyX: zoned iCalendar recurrence export is limited to 100 calendar years per time zone.");const start=Date.UTC(year,0,1),finish=Date.UTC(lastYear+1,0,1),changes=[];let previous=calIcsMinuteOffset(zone.offset(start));const initial=previous;for(let time=start+CAL_DAY;time<=finish;time +=CAL_DAY){const offset=calIcsMinuteOffset(zone.offset(time));if(offset===previous)continue;let low=time-CAL_DAY,high=time;while(high-low>CAL_MINUTE){const mid=low+Math.max(CAL_MINUTE,Math.floor((high-low)/ 2 / CAL_MINUTE) * CAL_MINUTE); if (calIcsMinuteOffset(zone.offset(mid)) === previous) low = mid; else high = mid; }
|
|
19
|
+
changes.push({at:high,from:previous,to:offset});previous=offset}
|
|
20
|
+
const lines=["BEGIN:VTIMEZONE","TZID:"+calIcsText(name)];lines.push("BEGIN:STANDARD","DTSTART:"+calIcsWall(zone,start),"TZOFFSETFROM:"+calIcsOffset(initial),"TZOFFSETTO:"+calIcsOffset(initial),"END:STANDARD");for(const change of changes){const local=change.at+change.from,date=new Date(local),d=date.getUTCDate(),month=date.getUTCMonth()+ 1,kind=change.to>change.from?"DAYLIGHT":"STANDARD";lines.push("BEGIN:"+kind,"DTSTART:"+calIcsDate(calDayNumber(date.getUTCFullYear(),month,d))+ "T"+String(date.getUTCHours()).padStart(2,"0")+ String(date.getUTCMinutes()).padStart(2,"0")+ "00");lines.push("TZOFFSETFROM:"+calIcsOffset(change.from),"TZOFFSETTO:"+calIcsOffset(change.to),"END:"+kind)}
|
|
21
|
+
lines.push("END:VTIMEZONE");return lines}
|
|
22
|
+
function calIcsRule(rule,zone,allDay){const parts=["FREQ="+rule.freq];if(rule.interval!==1)parts.push("INTERVAL="+rule.interval);if(rule.count!==null)parts.push("COUNT="+rule.count);if(rule.until)parts.push("UNTIL=" +(allDay?calIcsDate(rule.until.day??Math.floor(rule.until.time / CAL_DAY)) :
|
|
23
|
+
calIcsUtc(rule.until.time??(rule.until.minutes===undefined?zone.instant(rule.until.day+1,0)- 1000:zone.instant(rule.until.day,rule.until.minutes)))));if(rule.byMonth)parts.push("BYMONTH="+rule.byMonth.join(","));if(rule.byMonthDay)parts.push("BYMONTHDAY="+rule.byMonthDay.join(","));if(rule.byDay)parts.push("BYDAY="+rule.byDay.map(item=>(item.n||"")+ CAL_WEEKDAYS[item.dow]).join(","));if(rule.bySetPos)parts.push("BYSETPOS="+rule.bySetPos.join(","));if(rule.wkst!==1)parts.push("WKST="+CAL_WEEKDAYS[rule.wkst]);return parts.join(";")}
|
|
24
|
+
function calIcs(view,rows){const stamp=calIcsUtc(Date.now()),names=new Map(),zones=new Map(),events=[];for(const row of rows){const base=calBase(view,row);if(!base)continue;const zone=base.seriesZone;const lines=["BEGIN:VEVENT","UID:"+calIcsText(view.view.id+"-"+row.id+"@canopyx"),"DTSTAMP:"+stamp];let name;if(base.rule&&!base.allDay){name=names.get(zone.name);if(name===undefined){name=calIcsZoneName(zone);names.set(zone.name,name)}}
|
|
25
|
+
const zoned=base.rule&&!base.allDay&&name!=="UTC";if(zoned){const first=new Date(base.start).getUTCFullYear(),last=calIcsLastYear(base),prior=zones.get(name);zones.set(name,{zone,name,first:Math.min(first,prior?.first??first),last:Math.max(last,prior?.last??last)})}
|
|
26
|
+
const tz=zoned?";TZID="+calIcsText(name):"";const time=value=>base.allDay?";VALUE=DATE:"+calIcsDate(Math.floor(value / CAL_DAY)) : zoned ? tz + ":" + calIcsZonedWall(zone, value) : ":" + calIcsUtc(value);
|
|
27
|
+
lines.push("DTSTART"+time(base.start));if(base.end>base.start)lines.push("DTEND"+time(base.end));if(base.series&&Number.isFinite(base.recurrenceId)){lines.push("UID:"+calIcsText(view.view.id+"-"+base.series+"@canopyx"));lines.splice(1,1);lines.push("RECURRENCE-ID"+time(base.recurrenceId))}
|
|
28
|
+
lines.push("SUMMARY:"+calIcsText(base.title));const fields=base.external?row.fields:null;const detail=fields?fields.description||"":view.cal.description?cellText(row.cells[view.cal.description.id],view.cal.description,view.fmt):"";const description=[detail,base.subtitle,view.calStatusText({row})].filter(Boolean).join("\n");if(description)lines.push("DESCRIPTION:"+calIcsText(description));const location=fields?fields.location:base.resource?(view.calResourceName?view.calResourceName(base.resource):base.resource):"";if(location)lines.push("LOCATION:"+calIcsText(location));if(base.rule){lines.push("RRULE:"+calIcsRule(base.rule,zone,base.allDay));const minutes=zone.parts(base.start).minutes +((base.start%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE)/ CAL_MINUTE;
|
|
29
|
+
for(const day of base.excludedDays)lines.push(base.allDay?"EXDATE;VALUE=DATE:"+calIcsDate(day):"EXDATE"+time(zone.instant(day,minutes)));for(const value of base.excluded)lines.push("EXDATE" +(base.allDay?";VALUE=DATE:"+calIcsDate(Math.floor(value / CAL_DAY)) : time(value)));
|
|
30
|
+
}
|
|
31
|
+
if(base.background)lines.push("TRANSP:TRANSPARENT");lines.push("END:VEVENT");events.push(lines)}
|
|
32
|
+
const all=["BEGIN:VCALENDAR","VERSION:2.0","PRODID:-//CanopyX//Calendar//EN","CALSCALE:GREGORIAN"];if(view.view.label)all.push("X-WR-CALNAME:"+calIcsText(view.view.label));for(const item of zones.values())all.push(...calVTimezone(item.zone,item.name,item.first,item.last));for(const lines of events)all.push(...lines);all.push("END:VCALENDAR");return all.map(calIcsFold).join("\r\n")+ "\r\n"}
|
|
33
|
+
Object.assign(DataGrid.prototype,{async calIcsWrite(capture,context){this.calIcsExportColumns(capture.request.columns.map(column=>column.id));const rows=[];for await(const record of capture.rows({signal:context.signal})){const cells={};for(const[id,cell]of Object.entries(record.cells))cells[id]=cell&&typeof cell==="object"&&"value" in cell?cell.value:cell;rows.push({id:record.id,cells});context.progress(rows.length)}
|
|
34
|
+
this.calVersion++;return new Blob([calIcs(this,rows)],{type:"text/calendar;charset=utf-8"})},async calIcsExport(options={}){const scope=options.scope||"visible";if(!["visible","filtered","selected","all"].includes(scope))throw new Error("CanopyX: calendar export scopes are visible, filtered, selected and all.");let text;if(scope==="visible"){const answer=this.calAnswer;if(!answer||answer.signal.aborted||answer.epoch!==this.calEpoch||answer.query!==this.acceptedQuery||answer.source!==this.source||this.pendingQuery)
|
|
35
|
+
throw new Error("CanopyX: visible iCalendar export needs the current calendar answer; wait for the range to finish loading.");this.calIcsExportColumns();const seen=new Set(),rows=[];for(const event of this.calEvents){if(!seen.has(event.key)){seen.add(event.key);rows.push(event.row)}
|
|
36
|
+
if(event.base.series&&!seen.has(event.base.series)){const inline=this.view.source.kind==="inline"&&!this.opts.source;const series=answer.rows.find(row=>row.id===event.base.series)||(inline?this.calRowsCache?.rows?.find(row=>row.id===event.base.series):null);if(!series)throw new Error("CanopyX: visible iCalendar export needs the recurring master in the current calendar answer.");seen.add(series.id);rows.push(series)}}
|
|
37
|
+
text=calIcs(this,rows)}else{const blob=await this.exportAs("ics",{scope,values:"raw",download:false});text=await blob.blob.text()}
|
|
38
|
+
const fileName=(options.fileName||this.view.id.replace(/[^A-Za-z0-9_-]/g, "_")) + ".ics", blob = new Blob([text], { type: "text/calendar;charset=utf-8" });
|
|
39
|
+
if(options.download){const link=h("a");link.href=URL.createObjectURL(blob);link.download=fileName;document.body.appendChild(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(link.href),0)}
|
|
40
|
+
return{blob,fileName,text}}});const CAL_ICS_LIMITS=Object.freeze({text:2000000,bytes:8000000,events:5000,lines:75000,line:10000,short:1000,description:10000,exdates:1000,depth:16,zones:100,notes:100,series:1000,count:1000});const CAL_ICS_CONTROL=/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, CAL_ICS_DATE = /^(\d{4})(\d\d)(\d\d)$/, CAL_ICS_TIME = /^(\d{4})(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)(Z?)$/;
|
|
41
|
+
const CAL_ICS_DURATION=/^([+-])?P(?:(\d{1,5})W)?(?:(\d{1,5})D)?(?:T(?:(\d{1,5})H)?(?:(\d{1,6})M)?(?:(\d{1,7})S)?)?$/, CAL_ICS_ZONE = /^(?!.*\.\.)[A-Za-z0-9 /_+\-.()]{1,64}$/;
|
|
42
|
+
const calIcsLimit=what=>new Error("more than "+what);const CAL_ICS_SERIES_ZONES=new WeakMap();function calIcsUnescape(value){const text=value.replace(/\\([nN\\;,])/g, (all, char) => char === "n" || char === "N" ? "\n" : char).replace(CAL_ICS_CONTROL, char => char === "\t" || char === "\n" ? char : "");
|
|
43
|
+
return typeof text.toWellFormed==="function"?text.toWellFormed():text.replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "\ufffd");
|
|
44
|
+
}
|
|
45
|
+
function calIcsLine(line){const name=/^[A-Za-z0-9-]+/.exec(line)?.[0];
|
|
46
|
+
if(!name)return null;let at=name.length;const params=Object.create(null);while(line[at]===";"){const key=/^[A-Za-z0-9-]+/.exec(line.slice(at + 1))?.[0];
|
|
47
|
+
if(!key||line[at+1+key.length]!=="=")return null;at +=key.length+2;const values=[];for(;;){if(line[at]==="\""){const close=line.indexOf("\"",at+1);if(close<0)return null;values.push(line.slice(at+1,close));at=close+1}
|
|
48
|
+
else{const match=/^[^";:,]*/.exec(line.slice(at))[0]; values.push(match); at += match.length; }
|
|
49
|
+
if(line[at]!==",")break;at++}
|
|
50
|
+
params[key.toUpperCase()]=values}
|
|
51
|
+
if(line[at]!==":")return null;return{name:name.toUpperCase(),params,value:line.slice(at+1)}}
|
|
52
|
+
function calIcsZoneKnown(name){try{new Intl.DateTimeFormat("en",{timeZone:name});return true}catch{return false}}
|
|
53
|
+
function calIcsParse(text){const limits=CAL_ICS_LIMITS;if(typeof text!=="string")throw new Error("not text");if(text.length>limits.text)throw calIcsLimit(limits.text+" characters");const lines=[];for(const line of text.replace(/^\uFEFF/, "").split(/\r\n|\n|\r/)) {
|
|
54
|
+
if((line[0]===" "||line[0]==="\t")&&lines.length)lines[lines.length-1]+=line.slice(1);else if(line){lines.push(line);if(lines.length>limits.lines)throw calIcsLimit(limits.lines+" lines")}
|
|
55
|
+
if(lines.length&&lines[lines.length-1].length>limits.line)throw new Error("a line longer than "+limits.line+" characters")}
|
|
56
|
+
const notes=[],events=[],zones=new Map(),note=(index,text)=>{if(notes.length<limits.notes)notes.push("Event "+index+": "+text)};let index=0;const zoneOf=tzid=>{tzid=tzid.trim().replace(/^\//, "");
|
|
57
|
+
if(!zones.has(tzid)){const known=zones.size<limits.zones&&CAL_ICS_ZONE.test(tzid)&&calIcsZoneKnown(tzid);zones.set(tzid,known?calZone(tzid):null)}
|
|
58
|
+
return zones.get(tzid)};const when=(property,value=property.value)=>{const date=CAL_ICS_DATE.exec(value);if(date||property.params.VALUE?.[0]?.toUpperCase()==="DATE"){if(!date||+date[1]<1000)return null;const day=calParseDay(date[1]+ "-"+date[2]+ "-"+date[3]);return Number.isFinite(day)?{day}:null}
|
|
59
|
+
const match=CAL_ICS_TIME.exec(value);if(!match||+match[1]<1000)return null;const day=calParseDay(match[1]+ "-"+match[2]+ "-"+match[3]),hours=+match[4],minutes=+match[5],seconds=Math.min(+match[6],59);if(!Number.isFinite(day)||hours>23||minutes>59||+match[6]>60)return null;const wall=hours*60+minutes,tzid=match[7]?null:property.params.TZID?.[0];let zone=match[7]?null:tzid?zoneOf(tzid):null;if(!match[7]&&!zone){note(index,tzid?"its time zone is unknown here, read as UTC":"a time without a zone, read as UTC");zone=null}
|
|
60
|
+
const time=zone?zone.instant(day,wall)+ seconds*1000:day*CAL_DAY+wall*CAL_MINUTE+seconds*1000;return{time,zone,day,wall,seconds}};let depth=0,inCalendar=false,sawCalendar=false,current=null,nested=0,skipped=0;const series={count:0};for(const line of lines){const property=calIcsLine(line);if(!property){if(current)current.bad=true;continue}
|
|
61
|
+
const{name,value}=property;if(name==="BEGIN"){const what=value.trim().toUpperCase();if(++depth>limits.depth)throw calIcsLimit(limits.depth+" nested components");if(what==="VCALENDAR"&&depth===1){inCalendar=true;sawCalendar=true}
|
|
62
|
+
else if(what==="VEVENT"&&inCalendar&&depth===2&&!current){if(events.length+skipped+1>limits.events)throw calIcsLimit(limits.events+" events");index++;current={exdates:[],categories:0,bad:false}}else if(current)nested++;continue}
|
|
63
|
+
if(name==="END"){const what=value.trim().toUpperCase();if(current&&nested)nested--;else if(current&&what==="VEVENT"&&depth===2){const event=calIcsEvent(current,when,limits,series);if(event){events.push(event);if(event.unsupported)note(index,"a repeat rule outside the supported subset, or past "+limits.series+" repeating events, shown once");if(event.cut)note(index,"a repeat count over "+limits.count+" ends at the "+limits.count+"th occurrence")}else skipped++;current=null}else if(what==="VCALENDAR"&&depth===1)inCalendar=false;depth=Math.max(0,depth-1);continue}
|
|
64
|
+
if(!current||nested)continue;if(name==="EXDATE"){for(const item of value.split(",")){current.exdates.push({property,value:item.trim()});if(current.exdates.length>limits.exdates)throw calIcsLimit(limits.exdates+" exceptions in an event")}
|
|
65
|
+
continue}
|
|
66
|
+
if(name==="CATEGORIES"){current.categories +=calIcsUnescape(value).length;if(current.categories>limits.short)throw calIcsLimit(limits.short+" characters of CATEGORIES");const category=calIcsUnescape(value).trim();if(category)(current.categoryList||=[]).push(category);continue}
|
|
67
|
+
if(!(name in current))current[name]=property}
|
|
68
|
+
if(current)skipped++;if(!sawCalendar)throw new Error("no VCALENDAR");return{events,skipped,notes}}
|
|
69
|
+
function calIcsSeries(text,start,allDay,limits){const rule=calRule(text),zone=allDay||!start.zone?calZone("utc"):start.zone,at=allDay?start.day*CAL_DAY:start.time;const horizon={DAILY:1000,WEEKLY:7000,MONTHLY:30500,YEARLY:146100}[rule.freq]*rule.interval*CAL_DAY;if(rule.count!==1&&calExpand(rule,at,0,zone,at,at+horizon,new Set(),new Set(),2).length<2)throw new Error("no second occurrence");if(rule.count===null)return{recurrence:text,cut:false};const all=calExpand(rule,at,0,zone,-Infinity,Infinity,new Set(),new Set(),Math.min(rule.count,limits.count)),last=all[all.length-1];const parts=text.replace(/^RRULE:/i, "").split(";").filter(part => part && !/^(COUNT|UNTIL)=/i.test(part.trim()));
|
|
70
|
+
const until=allDay?calDayText(Math.floor(last / CAL_DAY)).replace(/-/g, "") : new Date(last).toISOString().slice(0, 19).replace(/[-:]/g, "") + "Z";
|
|
71
|
+
return{recurrence:[...parts,"UNTIL="+until].join(";"),cut:rule.count>limits.count}}
|
|
72
|
+
function calIcsEvent(item,when,limits,series){const text=(name,limit)=>{if(!item[name])return "";const value=calIcsUnescape(item[name].value).trim();if(value.length>limit)throw calIcsLimit(limit+" characters of "+name);return value};const title=text("SUMMARY",limits.short),description=text("DESCRIPTION",limits.description),location=text("LOCATION",limits.short);const optional=(name,limit)=>{const value=item[name]?calIcsUnescape(item[name].value).trim():"";return value.length<=limit?value:""};const uid=optional("UID",limits.short),status=optional("STATUS",64).toUpperCase();if(item.bad||!item.DTSTART)return null;const start=when(item.DTSTART);if(!start)return null;const allDay=start.day!==undefined&&start.time===undefined;let end=item.DTEND?when(item.DTEND):null;if(item.DTEND&&!end)return null;if(!end&&item.DURATION){const match=CAL_ICS_DURATION.exec(item.DURATION.value.trim());if(!match)return null;const days=+(match[2]||0)*7 + +(match[3]||0),exact=(+(match[4]||0)*60 + +(match[5]||0))*CAL_MINUTE + +(match[6]||0)*1000;if(match[1]==="-")end=null;else if(allDay)end={day:start.day+days +(exact>0?Math.ceil(exact / CAL_DAY) : 0) };
|
|
73
|
+
else end={time:(start.zone?start.zone.instant(start.day+days,start.wall)+ start.seconds*1000:start.time+days*CAL_DAY)+ exact}}
|
|
74
|
+
let endText=null;if(allDay&&end){const day=end.time===undefined?end.day:Math.ceil(end.time / CAL_DAY); if (day > start.day) endText = calDayText(day) + "T00:00:00Z"; }
|
|
75
|
+
else if(!allDay&&end){const time=end.time??end.day*CAL_DAY;if(time>start.time)endText=calIso(time)}
|
|
76
|
+
const exceptions=[];for(const{property,value}of item.exdates){if(!value)continue;const at=when(property,value);if(!at)return null;exceptions.push(at.time===undefined?calDayText(at.day):calIso(at.time))}
|
|
77
|
+
let recurrence=null,unsupported=false,cut=false;if(item.RRULE&&series.count>=limits.series)unsupported=true;else if(item.RRULE){try{({recurrence,cut}=calIcsSeries(item.RRULE.value.trim(),start,allDay,limits));series.count++}catch{unsupported=true}}
|
|
78
|
+
const recurrenceId=item["RECURRENCE-ID"]?when(item["RECURRENCE-ID"]):null;if(item["RECURRENCE-ID"]&&!recurrenceId)return null;const event={uid,title,description,location,categories:(item.categoryList||[]).join(","),status,start:allDay?calDayText(start.day)+ "T00:00:00Z":calIso(start.time),end:endText,allDay,recurrence,unsupported,cut,exceptions:exceptions.join(","),recurrenceId:recurrenceId?(recurrenceId.time===undefined?calDayText(recurrenceId.day)+ "T00:00:00Z":calIso(recurrenceId.time)):null};if(recurrence&&!allDay)CAL_ICS_SERIES_ZONES.set(event,start.zone?.name||"utc");return event}
|
|
79
|
+
Object.assign(DataGrid.prototype,{async calLoadIcs(){const rows=[],errors=[],signal=this.lifetime.signal;await Promise.all(this.cal.sources.filter(source=>source.ics).map(async source=>{try{const text=await calIcsFetch(source.ics,signal),result=calIcsParse(text);rows.push(...calIcsRows(source.id,result.events));this.emit("calendarImport",{source:source.id,events:result.events.length,skipped:result.skipped,notes:result.notes})}catch(error){if(error.name==="AbortError")return;errors.push(fill(this.calLabels.calendarImportFailed,{source:source.label,error:String(error.message||error).slice(0,200)}));this.emit("error",{error,source:source.id})}}));this.calIcsRows=rows;this.calIcsErrors=errors}});async function calIcsFetch(address,signal){const url=new URL(address,location.href);if(location.origin==="null"||url.origin!==location.origin||!/^https?:$/.test(url.protocol)) throw new Error("not on this site");
|
|
80
|
+
const response=await fetch(url,{credentials:"same-origin",signal,headers:{Accept:"text/calendar"}});if(!response.ok)throw new Error("HTTP "+response.status);if(new URL(response.url||url.href).origin!==location.origin)throw new Error("not on this site");const length=Number(response.headers.get("content-length"));if(length>CAL_ICS_LIMITS.bytes)throw new Error("the file is too large");if(!response.body?.getReader){const text=await response.text();if(text.length>CAL_ICS_LIMITS.text)throw new Error("the file is too large");return text}
|
|
81
|
+
const reader=response.body.getReader(),decoder=new TextDecoder("utf-8");let text="",bytes=0;for(;;){const{done,value}=await reader.read();if(done)break;bytes +=value.byteLength;if(bytes>CAL_ICS_LIMITS.bytes){reader.cancel().catch(()=>{});throw new Error("the file is too large")}
|
|
82
|
+
text +=decoder.decode(value,{stream:true})}
|
|
83
|
+
return text+decoder.decode()}
|
|
84
|
+
function calIcsRows(source,events){const rows=[],used=new Map(),emitted=new Set(),byUid=new Map(),cancelled=[];events.forEach((event,index)=>{const series="ics:"+source+":" +(event.uid||"e" +(index+1));if(event.recurrenceId){if(event.status==="CANCELLED"){cancelled.push([series,event.recurrenceId]);return}}else if(event.status==="CANCELLED")return;const base=event.recurrenceId?series+"@"+event.recurrenceId:series;let seen=used.get(base)||0,id=seen?base+"+"+seen:base;while(emitted.has(id))id=base+"+" + ++seen;used.set(base,seen+1);emitted.add(id);const row={id,cells:Object.create(null),external:source,fields:{title:event.title,start:event.start,end:event.end,allDay:event.allDay,recurrence:event.recurrenceId?null:event.recurrence,exceptions:event.exceptions,series:event.recurrenceId?series:null,recurrenceId:event.recurrenceId,description:event.description,location:event.location,seriesZone:CAL_ICS_SERIES_ZONES.get(event)}};if(!event.recurrenceId&&!byUid.has(series))byUid.set(series,row);rows.push(row)});const extra=new Map();for(const[series,at]of cancelled){const list=extra.get(series)||extra.set(series,[]).get(series);if(list.length<CAL_ICS_LIMITS.exdates)list.push(at)}
|
|
85
|
+
for(const[series,list]of extra){const row=byUid.get(series);if(!row?.fields.recurrence)continue;const all=[...(row.fields.exceptions?row.fields.exceptions.split(","):[]),...list].slice(0,CAL_ICS_LIMITS.exdates);row.fields.exceptions=all.join(",")}
|
|
86
|
+
return rows}
|
|
87
|
+
Object.assign(DataGrid.prototype.calKit,{calIcsText,calIcsFold,calIcsParse});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["calendar-ics",part])})(typeof window!=="undefined"?window:globalThis);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
(function(global){"use strict";const installed=new WeakSet();const part={version:"64bcc24d785bd1af",requires:["calendar"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid}=context;const{CAL_DAY,CAL_MINUTE,calDayNumber,calDayParts,calDow,calMonthDays,calParseDay}=DataGrid.prototype.calKit;const CAL_WEEKDAYS=["SU","MO","TU","WE","TH","FR","SA"],CAL_FREQS=["DAILY","WEEKLY","MONTHLY","YEARLY"];const CAL_RULE_CACHE=new Map(),CAL_MAX_PERIODS=60000;function calRule(text){if(CAL_RULE_CACHE.has(text))return CAL_RULE_CACHE.get(text);if(typeof text!=="string"||text.length>1024)throw new Error("CanopyX: a recurrence rule is text of at most 1024 characters.");const rule={interval:1,byDay:null,byMonthDay:null,byMonth:null,bySetPos:null,wkst:1,count:null,until:null};const seen=new Set(),list=(value,check)=>value.split(",").map(item=>{if(!check(item))throw new Error("CanopyX: invalid recurrence value "+item+".");return item});const integer=(value,min,max)=>/^[+-]?\d{1,4}$/.test(value) && +value >= min && +value <= max && +value !== 0;
|
|
2
|
+
for(const part of text.trim().replace(/^RRULE:/i, "").split(";")) {
|
|
3
|
+
const at=part.indexOf("="),name=part.slice(0,at).trim().toUpperCase(),value=part.slice(at+1).trim().toUpperCase();if(at<1||!value||seen.has(name))throw new Error("CanopyX: invalid recurrence rule part "+part+".");seen.add(name);if(name==="FREQ"){if(!CAL_FREQS.includes(value))throw new Error("CanopyX: recurrence FREQ is DAILY, WEEKLY, MONTHLY or YEARLY.");rule.freq=value}
|
|
4
|
+
else if(name==="INTERVAL"){if(!integer(value,1,9999))throw new Error("CanopyX: invalid recurrence INTERVAL.");rule.interval=+value}
|
|
5
|
+
else if(name==="COUNT"){if(!integer(value,1,9999))throw new Error("CanopyX: invalid recurrence COUNT.");rule.count=+value}
|
|
6
|
+
else if(name==="UNTIL")rule.until=calRuleTime(value);else if(name==="BYDAY")rule.byDay=list(value,item=>/^([+-]?\d{1,2})?(SU|MO|TU|WE|TH|FR|SA)$/.test(item) && (!/\d/.test(item) || integer(item.slice(0, -2), -53, 53)))
|
|
7
|
+
.map(item=>({n:item.length>2?+item.slice(0,-2):0,dow:CAL_WEEKDAYS.indexOf(item.slice(-2))}));else if(name==="BYMONTHDAY")rule.byMonthDay=list(value,item=>integer(item,-31,31)).map(Number);else if(name==="BYMONTH")rule.byMonth=list(value,item=>integer(item,1,12)).map(Number);else if(name==="BYSETPOS")rule.bySetPos=list(value,item=>integer(item,-366,366)).map(Number);else if(name==="WKST"){if(!CAL_WEEKDAYS.includes(value))throw new Error("CanopyX: invalid recurrence WKST.");rule.wkst=CAL_WEEKDAYS.indexOf(value)}
|
|
8
|
+
else throw new Error("CanopyX: recurrence rules do not support "+name+".")}
|
|
9
|
+
if(!rule.freq||rule.count!==null&&rule.until!==null)throw new Error("CanopyX: a recurrence rule needs FREQ, and COUNT or UNTIL, not both.");if(rule.byDay&&rule.byDay.some(item=>item.n)&&(rule.freq==="DAILY"||rule.freq==="WEEKLY"))
|
|
10
|
+
throw new Error("CanopyX: numbered BYDAY values belong to monthly and yearly rules.");if(rule.byMonthDay&&rule.freq==="WEEKLY")throw new Error("CanopyX: weekly rules do not take BYMONTHDAY.");if(CAL_RULE_CACHE.size>512)CAL_RULE_CACHE.clear();CAL_RULE_CACHE.set(text,rule);return rule}
|
|
11
|
+
function calRuleTime(value){const match=/^(\d{4})(\d\d)(\d\d)(?:T(\d\d)(\d\d)(\d\d)(Z)?)?$/.exec(value) || /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d):(\d\d):(\d\d)(Z)?)?$/.exec(value);
|
|
12
|
+
const day=match?calParseDay(match[1]+ "-"+match[2]+ "-"+match[3]):NaN;if(!Number.isFinite(day)||match[4]!==undefined&&(+match[4]>23||+match[5]>59||+match[6]>59))throw new Error("CanopyX: invalid recurrence UNTIL.");if(match[4]===undefined)return{day};const minutes=+match[4]*60 + +match[5],seconds=+match[6];return match[7]?{time:day*CAL_DAY+minutes*CAL_MINUTE+seconds*1000}:{day,minutes:minutes+seconds / 60 };
|
|
13
|
+
}
|
|
14
|
+
function calPeriodDays(rule,period,first){const days=[];const monthDays=(y,m)=>{const count=calMonthDays(y,m),start=calDayNumber(y,m,1),out=[];let picked=null;if(rule.byMonthDay)picked=new Set(rule.byMonthDay.map(d=>d>0?d:count+d+1).filter(d=>d>=1&&d<=count));if(rule.byDay){const byDay=new Set();for(const item of rule.byDay){const matches=[];for(let d=1;d<=count;d++)if(calDow(start+d-1)===item.dow)matches.push(d);if(!item.n)matches.forEach(d=>byDay.add(d));else{const d=item.n>0?matches[item.n-1]:matches[matches.length+item.n];if(d)byDay.add(d)}}
|
|
15
|
+
picked=picked?new Set([...picked].filter(d=>byDay.has(d))):byDay}
|
|
16
|
+
if(!picked)picked=new Set(first.d<=count?[first.d]:[]);for(const d of[...picked].sort((a,b)=>a-b))out.push(start+d-1);return out};if(rule.freq==="DAILY"){const day=period,parts=calDayParts(day);if((!rule.byMonth||rule.byMonth.includes(parts.m))&&(!rule.byDay||rule.byDay.some(item=>item.dow===parts.dow))&&
|
|
17
|
+
(!rule.byMonthDay||rule.byMonthDay.some(d=>(d>0?d:calMonthDays(parts.y,parts.m)+ d+1)===parts.d)))days.push(day)}else if(rule.freq==="WEEKLY"){const dows=rule.byDay?rule.byDay.map(item=>item.dow):[first.dow];for(let i=0;i<7;i++){const day=period+i,parts=calDayParts(day);if(dows.includes(parts.dow)&&(!rule.byMonth||rule.byMonth.includes(parts.m)))days.push(day)}}else if(rule.freq==="MONTHLY"){const y=Math.floor(period / 12), m = period - y * 12 + 1;
|
|
18
|
+
if(!rule.byMonth||rule.byMonth.includes(m))days.push(...monthDays(y,m))}else{const y=period;if(!rule.byMonth&&(rule.byDay||rule.byMonthDay)){const start=calDayNumber(y,1,1),count=calDayNumber(y+1,1,1)- start;for(let d=0;d<count;d++){const day=start+d,parts=calDayParts(day);if(rule.byMonthDay&&!rule.byMonthDay.some(value=>(value>0?value:calMonthDays(parts.y,parts.m)+ value+1)===parts.d))continue;if(rule.byDay&&!rule.byDay.some(item=>item.dow===parts.dow&&(!item.n||item.n===Math.floor(d / 7) + 1 || item.n === -(Math.floor((count - 1 - d) / 7) + 1)))) continue;
|
|
19
|
+
days.push(day)}}else for(const m of rule.byMonth||[first.m])days.push(...monthDays(y,m));days.sort((a,b)=>a-b)}
|
|
20
|
+
if(!rule.bySetPos)return days;return[...new Set(rule.bySetPos.map(pos=>pos>0?days[pos-1]:days[days.length+pos]).filter(day=>day!==undefined))].sort((a,b)=>a-b)}
|
|
21
|
+
function calExpand(rule,start,duration,zone,from,to,excluded,excludedDays,limit){const first=zone.parts(start),out=[],minutes=first.minutes +(start%CAL_MINUTE+CAL_MINUTE)%CAL_MINUTE / CAL_MINUTE;
|
|
22
|
+
const until=rule.until?rule.until.time??(rule.until.minutes===undefined?zone.instant(rule.until.day+1,0)- 1:zone.instant(rule.until.day,rule.until.minutes)):Infinity;let count=0,period;if(rule.freq==="DAILY")period=first.day;else if(rule.freq==="WEEKLY")period=first.day -(first.dow-rule.wkst+7)%7;else if(rule.freq==="MONTHLY")period=first.y*12+first.m-1;else period=first.y;const stride=rule.freq==="WEEKLY"?7*rule.interval:rule.interval;const periodDay=value=>rule.freq==="DAILY"||rule.freq==="WEEKLY"?value:rule.freq==="MONTHLY"?calDayNumber(Math.floor(value / 12), value % 12 + 1, 1) : calDayNumber(value, 1, 1);
|
|
23
|
+
if(rule.count===null&&Number.isFinite(from)){const target=zone.day(from-Math.max(duration,0))- 1,at=zone.parts(zone.start(target));const index=rule.freq==="DAILY"||rule.freq==="WEEKLY"?target:rule.freq==="MONTHLY"?at.y*12+at.m-1:at.y;const steps=Math.floor((index-period)/ stride) - 1;
|
|
24
|
+
if(steps>0)period +=steps*stride}
|
|
25
|
+
const take=time=>{count++;if(time+Math.max(duration,0)>from&&time<to||duration<=0&&time>=from&&time<to){const day=zone.day(time);if(!excluded.has(time)&&!excludedDays.has(day))out.push(time)}};if(start>until)return out;take(start);for(let step=0;step<CAL_MAX_PERIODS&&out.length<limit;step++,period +=stride){if(zone.start(periodDay(period))- CAL_DAY>=Math.min(to,until))break;let past=false;for(const day of calPeriodDays(rule,period,first)){if(day<first.day)continue;const time=zone.instant(day,minutes);if(time<=start)continue;if(time>until||rule.count!==null&&count>=rule.count){past=true;break}
|
|
26
|
+
if(time>=to){past=true;break}
|
|
27
|
+
take(time)}
|
|
28
|
+
if(past)break}
|
|
29
|
+
return out}
|
|
30
|
+
function calSeriesEnd(rule,start,duration,zone){if(rule.count===null&&rule.until===null)return Infinity;const all=calExpand(rule,start,duration,zone,-Infinity,Infinity,new Set(),new Set(),rule.count??100000);return all.length?all[all.length-1]+ Math.max(duration,0):start+Math.max(duration,0)}
|
|
31
|
+
const CAL_EXCEPTION=/^(\d{4}-\d\d-\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,7})?)?(Z|[+-]\d\d:\d\d)?$/;
|
|
32
|
+
function calExceptions(value,zone){const excluded=new Set(),excludedDays=new Set();if(typeof value!=="string")return{excluded,excludedDays};const items=value.split(/[\s,;]+/).filter(Boolean);
|
|
33
|
+
if(items.length>1000)throw new Error("CanopyX: a series takes at most 1000 exceptions.");for(const item of items){const day=calParseDay(item),match=CAL_EXCEPTION.exec(item);if(Number.isFinite(day)){excludedDays.add(day);continue}
|
|
34
|
+
const date=match?calParseDay(match[1]):NaN;if(!Number.isFinite(date)||+match[2]>23||+match[3]>59||match[4]!==undefined&&+match[4]>59)throw new Error("CanopyX: invalid recurrence exception.");const minutes=+match[2]*60 + +match[3],seconds=+(match[4]||0)+ +(match[5]||0);excluded.add(match[6]?Date.parse(match[1]+ "T"+match[2]+ ":"+match[3]+ ":"+String(match[4]||"00")+ match[6])+ Math.round(+(match[5]||0)*1000):zone.instant(date,minutes)+ Math.round(seconds*1000))}
|
|
35
|
+
return{excluded,excludedDays}}
|
|
36
|
+
Object.assign(DataGrid.prototype.calKit,{calRule,calExpand,calSeriesEnd,calExceptions,CAL_WEEKDAYS});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["calendar-recurrence",part])})(typeof window!=="undefined"?window:globalThis);
|