@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,18 +1,18 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"bdde26d4b7de2096",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);
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"29a711a1cfd4be9a",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||this.cal.resourceIds)||
2
+ this.calResourceValues(event,resource).some(([id])=>!this.canEditCell(this.editRecord?.(event.key)||event.row,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
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})}}
4
+ const resources=this.calEventResources(event).map(id=>id===event.resource&&resource!==undefined?resource:id);if(resource!=null&&!resources.includes(resource))resources.push(resource);if(!resources.length)resources.push(null);const spanProblem=(resources.length?resources:[null]).map(resource=>this.calSpanProblem({...proposal,resource})).find(Boolean);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.base.identity===event.base.identity||other.background||((this.cal.resource||this.cal.resourceIds)&&!resources.some(id=>this.calHasResource(other,id))))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
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||
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.base.identity!==event.base.identity||
7
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)))
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=[...new Set([item.resource,...(item.resourceIds||[])])].map(resource=>this.calSpanProblem({...item,resource,allDay:proposal.allDay})).find(Boolean);if(problem)return problem}
9
+ if(this.cal.editable.overlap)return null;const assignments=items.flatMap(item=>{const ids=item.resourceIds?.length?[...new Set([item.resource,...item.resourceIds].filter(Boolean))]:[item.resource];return ids.map(resource=>({...item,resource}))});assignments.sort((a,b)=>a.start-b.start||b.end-a.end);const active=new Map();for(const item of assignments){const resource=this.cal.resource||this.cal.resourceIds?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
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
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
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
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
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}
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)values.push(...this.calResourceValues(event,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
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
17
  const previous=cellValue(record?.cells[column]);if(!this.editApply(event.key,spec,value,"calendar",record)){failed=false;return}
18
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}
@@ -20,7 +20,7 @@ this.calVersion++;const time=this.calFmt.shortDay(this.calZone.day(proposal.star
20
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
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
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}
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&&!item.base?.external);return event?JSON.parse(JSON.stringify(event.base.row)):found};DataGrid.prototype.editRecord._calendar=true}
24
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
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
26
  const drag=this.calDrag;if(!drag||pointer.pointerId!==drag.id)return;if(drag.cancelled){pointer.preventDefault();return}
@@ -55,7 +55,7 @@ const detail={start:calIso(proposal.start),end:calIso(proposal.end),allDay:propo
55
55
  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),
56
56
  ed:proposal.allDay?Math.floor((proposal.end-1)/ CAL_DAY) : this.calZone.day(Math.max(proposal.start, proposal.end - 1)), marker: false }));
57
57
  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}
58
- 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.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;else if(!cal.resource&&cal.resourceIds?.editable&&proposal.resource!=null)cells[cal.resourceIds.id]=JSON.stringify([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));
59
59
  this.calAnnounce(fill(this.calLabels.calendarCreated,{title,time}));this.calVersion++;if(!result.valid){this.calInvalidInsert(result);return true}
60
60
  if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
61
61
  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}
@@ -83,27 +83,28 @@ const calMoveTime=(time,zone,minutes)=>minutes?zone.instant(zone.day(time),zone.
83
83
  }
84
84
  const value=until.time!==undefined?calUntilText(shifted,false):
85
85
  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]))}
86
- 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
+ Object.assign(DataGrid.prototype,{calEditSnapshot(event){const columns=[this.cal.start,this.cal.end,this.cal.allDay,this.cal.resource,this.cal.resourceIds,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||
87
87
  this.source!==decision.source||this.edit!==decision.edit||this.edit?.options!==decision.options||
88
- 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)}
89
- 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}]}}
88
+ 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,resource:this.calHasResource(current,event.resource)?event.resource:current.resource,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)}
89
+ 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);for(const[id,value]of this.calResourceValues(event,proposal.resource))cells[id]=value;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,resourceIds:this.calEventResources(event).map(id=>id===event.resource?resource:id)}]}}
90
90
  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)}
91
- 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
+ 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.base.identity===event.base.identity).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))}
92
92
  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)}
93
93
  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?
94
94
  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))&&
95
95
  (loadedDays.has(zone.day(time))||calOverlaps(time,time+duration,from,to)))
96
- .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
+ .map(time=>({start:time,end:time+duration,allDay:base.allDay,resource,resourceIds:this.calEventResources(event).map(id=>id===event.resource?resource:id)}));const values=[[cal.start.id,cells[cal.start.id]]];for(const column of[cal.end,cal.resource,cal.resourceIds,cal.recurrence,cal.exceptions].filter(Boolean))
97
97
  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}))}
98
98
  catch(error){this.emit("error",{error});return null}
99
99
  if(decided===false)return null;if(["this","following","all"].includes(decided))mode=decided}
100
100
  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)}
101
101
  else if(key.key==="Tab"){key.preventDefault();buttons[(at +(key.shiftKey?buttons.length-1:1))%buttons.length].focus()}
102
102
  else if(key.key==="ArrowDown"||key.key==="ArrowRight"){key.preventDefault();buttons[(at+1)%buttons.length].focus()}
103
- 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}
104
- 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}
103
+ 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,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}
104
+ const exception=cal.exceptions&&this.edit.specs.get(cal.exceptions.id),master=this.editRecord(event.key);let result,excluded=false;if(exception&&this.canEditCell(master,exception.id)){const previous=String(cellValue(master.cells[exception.id])||"").trim();if(previous.split(/[\s,;]+/).filter(Boolean).length >= 1000) { this.calNotice(labels.calendarSeriesTime, true, true); return false; }
105
+ const done=this.editGroupIf(()=>{result=this.editInsertNow({cells},"calendar");if(!result.applied)return false;excluded=this.editApply(event.key,exception,previous?previous+","+calIso(event.start):calIso(event.start),"calendar",master);if(!excluded)this.editRemoveInserted(result.id);return excluded});if(!done)return false;const checked=await this.editValidateRow(result.id);result={...result,...checked}}else 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}
105
106
  if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
106
- 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}
107
+ return this.calCommit(excluded?[event.key,result.id]:[result.id],event.title,excluded)},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}
107
108
  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}
108
109
  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}
109
110
  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))}
@@ -111,4 +112,21 @@ finally{if(!cut)this.editRemoveInserted(inserted.id)}
111
112
  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}
112
113
  if((cal.editable?.commit??"auto")==="manual"){this.calReload({keep:true});return true}
113
114
  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}
114
- 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);
115
+ 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})}});Object.assign(DataGrid.prototype,{async editCalendarOccurrence(id){const event=this.calEventTarget(id)?._calEvent||this.calEvents.find(item=>item.id===id);if(!event?.recurring||!this.cal.series?.editable||!this.cal.recurrenceId?.editable||!this.calCanEdit(event,false))return false;await this.loadEditing();calWrapEditRecord();const decision=this.calEditSnapshot(event),current=this.calEditCurrent(event,decision);if(!current)return false;this.calOccurrenceForm(current,decision);return true},calOccurrenceForm(event,decision){this.calCloseOccurrence(false);this.calCloseDetails(false);this.calHideHover();const panel=this.calOccurrenceEditor=h("form","cx-cal-create cx-cal-occurrence-editor"),heading=h("h3","cx-cal-create-h");const labels=this.calLabels,titleId=this.id+"-cal-occurrence",fields=new Map();heading.id=titleId;setText(heading,labels.calendarEditOccurrence);panel.setAttribute("role","dialog");panel.setAttribute("aria-modal","false");panel.setAttribute("aria-labelledby",titleId);panel.appendChild(heading);const when=h("p","cx-cal-det-when");when.id=titleId+"-scope";setText(when,fill(labels.calendarOccurrenceScope,{date:this.calFmt.day({weekday:"short",day:"numeric",month:"short"},event.sd),title:event.title}));panel.setAttribute("aria-describedby",when.id);panel.appendChild(when);const resources=this.calEventResources(event);if(resources.length>1){const line=h("p","cx-cal-det-when");setText(line,resources.map(id=>this.calResourceName?.(id)||id).join(", "));panel.appendChild(line)}
116
+ const wall=time=>{if(event.allDay)return calDayText(Math.floor(time / CAL_DAY));
117
+ const minutes=this.calZone.parts(time).minutes,pad=value=>String(value).padStart(2,"0");return calDayText(this.calZone.day(time))+ "T"+pad(Math.floor(minutes / 60)) + ":" + pad(minutes % 60);
118
+ };const addField=(name,text,value,type,column)=>{const label=h("label","cx-cal-create-l"),input=h("input","cx-cal-create-input");input.id=titleId+"-"+name;input.name=name;input.type=type;input.value=value;input.required=true;input.disabled=!column?.editable||!this.canEditCell(event.row,column.id);if(type==="text")input.maxLength=1000;else if(type==="datetime-local")input.step="60";label.htmlFor=input.id;setText(label,text);fields.set(name,input);panel.append(label,input)};addField("title",labels.calendarEventTitle,event.title,"text",this.cal.title);addField("start",this.cal.start.title,wall(event.start),event.allDay?"date":"datetime-local",this.cal.start);if(this.cal.end)addField("end",this.cal.end.title,wall(event.end),event.allDay?"date":"datetime-local",this.cal.end);const original=new Map([...fields].map(([name,input])=>[name,input.value]));const message=h("p","cx-cal-occurrence-error");message.setAttribute("role","status");message.hidden=true;const actions=h("div","cx-cal-create-actions"),cancel=h("button","cx-btn ghost"),save=h("button","cx-btn cx-cal-create-add");cancel.type="button";save.type="submit";setText(cancel,labels.calendarCancel);setText(save,this.edit.labels.gridEditSave);cancel.addEventListener("click",()=>this.calCloseOccurrence(true));const remove=h("button","cx-btn ghost cx-cal-occurrence-remove");remove.type="button";setText(remove,labels.calendarRemoveOccurrence);remove.disabled=!this.cal.exceptions?.editable||!this.canEditCell(event.row,this.cal.exceptions.id);remove.addEventListener("click",async()=>{save.disabled=remove.disabled=true;try{const current=this.calEditCurrent(event,decision);if(!current){this.calRefuseDecision();this.calCloseOccurrence(true);return}
119
+ if(await this.calRemoveOccurrence(current))this.calCloseOccurrence(true);else{setText(message,this.calNoticeElement.textContent||this.edit.labels.gridEditNotSaved);message.hidden=false}}catch(error){this.emit("error",{error});setText(message,this.edit.labels.gridEditNotSaved);message.hidden=false}
120
+ finally{save.disabled=remove.disabled=false}});actions.append(remove,cancel,save);panel.append(message,actions);panel._calAnchor=this.calEventTarget(event.id);this.calPlace(panel,panel._calAnchor||this.calBody);if(this.calPhone){panel.classList.add("cx-cal-occurrence-sheet");panel.style.removeProperty("width");panel.style.removeProperty("left");panel.style.removeProperty("top")}
121
+ else{const box=panel.getBoundingClientRect(),frame=this.calRoot.getBoundingClientRect();let top=box.top;for(const card of this.calBody.querySelectorAll(".cx-cal-ev")){const other=card.getBoundingClientRect();if(card.dataset.cxEvent!==event.id&&other.left<box.right&&other.right>box.left&&other.bottom>top&&other.top<top+box.height)
122
+ top=Math.max(top,other.bottom+6)}
123
+ if(top+box.height<=frame.bottom-8)panel.style.top=(top-frame.top)+ "px"}
124
+ panel.addEventListener("keydown",key=>{if(key.key==="Escape"){key.preventDefault();key.stopPropagation();this.calCloseOccurrence(true)}
125
+ if(key.key==="Tab"){const stops=[...panel.querySelectorAll("input:not(:disabled),button:not(:disabled)")],at=stops.indexOf(document.activeElement);key.preventDefault();stops[(at +(key.shiftKey?stops.length-1:1))%stops.length]?.focus()}});panel.addEventListener("submit",async submit=>{submit.preventDefault();save.disabled=remove.disabled=true;try{const current=this.calEditCurrent(event,decision);if(!current){this.calRefuseDecision();this.calCloseOccurrence(true);return}
126
+ const read=name=>{const value=fields.get(name)?.value,day=this.calKit.calParseDay(value?.slice(0,10));if(value===original.get(name))return event[name];if(event.allDay)return day*CAL_DAY;const parts=/^\d{4}-\d\d-\d\dT(\d\d):(\d\d)(?::(\d\d(?:\.\d+)?))?$/.exec(value || "");
127
+ return parts?this.calZone.instant(day,Number(parts[1])*60+Number(parts[2]))+ Number(parts[3]||0)*1000:NaN};const proposal={start:read("start"),end:fields.has("end")?read("end"):read("start"),allDay:event.allDay,resource:event.resource};if(!Number.isFinite(proposal.start)||!Number.isFinite(proposal.end)||proposal.end<proposal.start){setText(message,labels.calendarOutsideRange);message.hidden=false;return}
128
+ if(!this.emit("occurrenceChange",{id:event.key,occurrence:calIso(event.start),start:calIso(proposal.start),end:calIso(proposal.end),resource:event.resource}))return;const plan=this.calOccurrencePlan(current,proposal,"this"),title=fields.get("title");if(!title.disabled)plan.cells[this.cal.title.id]=title.value;if(await this.calApplyThis(current,proposal,plan,decision))this.calCloseOccurrence(true);else{setText(message,this.calNoticeElement.textContent||this.edit.labels.gridEditNotSaved);message.hidden=false}}catch(error){this.emit("error",{error});setText(message,this.edit.labels.gridEditNotSaved);message.hidden=false}
129
+ finally{save.disabled=false;remove.disabled=!this.cal.exceptions?.editable||!this.canEditCell(event.row,this.cal.exceptions.id)}});[...fields.values()].find(input=>!input.disabled)?.focus({preventScroll:true})},async calRemoveOccurrence(event){const column=this.cal.exceptions,record=this.editRecord(event.key),spec=column&&this.edit.specs.get(column.id);if(!spec||!this.canEditCell(record,column.id))return false;const previous=String(cellValue(record.cells[column.id])||"").trim();if(previous.split(/[\s,;]+/).filter(Boolean).length >= 1000) return false;
130
+ const value=previous?previous+","+calIso(event.start):calIso(event.start);if(!this.editApply(event.key,spec,value,"calendar",record))return false;const checked=await this.editValidateRow(event.key);if(!checked.valid){this.calInvalidInsert({id:event.key,errors:checked.errors});return false}
131
+ this.calVersion++;this.calCache?.clear();if(this.cal.editable.commit==="manual"){this.calReload({keep:true});return true}
132
+ return this.calCommit([event.key],event.title)},calCloseOccurrence(restore){const panel=this.calOccurrenceEditor;if(!panel)return;this.calOccurrenceEditor=null;panel.remove();if(restore){if(panel._calAnchor?.isConnected)panel._calAnchor.focus({preventScroll:true});else this.calRestoreFocus(true)}}});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["calendar-edit",part])})(typeof window!=="undefined"?window:globalThis);
@@ -1,4 +1,4 @@
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");
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"ac8b2950fccbfb0f",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
2
  }
3
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
4
  current +=char;size +=bytes}
@@ -21,10 +21,12 @@ const lines=["BEGIN:VTIMEZONE","TZID:"+calIcsText(name)];lines.push("BEGIN:STAND
21
21
  lines.push("END:VTIMEZONE");return lines}
22
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
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)}}
24
+ function calIcs(view,rows){const stamp=calIcsUtc(Date.now()),names=new Map(),zones=new Map(),events=[];const masters=new Map();for(const row of rows){const base=calBase(view,row);if(base?.rule&&!base.series)masters.set(base.identity,base)}
25
+ 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+"-"+base.identity+"@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
26
  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
27
  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("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.seriesIdentity+"@canopyx"));lines.splice(1,1);const original=masters.get(base.seriesIdentity);lines.push("RECURRENCE-ID" +(original?.allDay?";VALUE=DATE:"+calIcsDate(Math.floor(base.recurrenceId / CAL_DAY)) : ":" + calIcsUtc(base.recurrenceId)));
29
+ }
28
30
  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
31
  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
32
  }
@@ -32,8 +34,8 @@ if(base.background)lines.push("TRANSP:TRANSPARENT");lines.push("END:VEVENT");eve
32
34
  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
35
  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
36
  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
+ 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.base.identity)){seen.add(event.base.identity);rows.push(event.row)}
38
+ if(event.base.series&&!seen.has(event.base.seriesIdentity)){const inline=this.view.source.kind==="inline"&&!this.opts.source;const matches=row=>row.id===event.base.series&&(row.external||null)===(event.row.external||null);const series=answer.rows.find(matches)||(inline?this.calRowsCache?.rows?.find(matches):null);if(!series)throw new Error("CanopyX: visible iCalendar export needs the recurring master in the current calendar answer.");seen.add(event.base.seriesIdentity);rows.push(series)}}
37
39
  text=calIcs(this,rows)}else{const blob=await this.exportAs("ics",{scope,values:"raw",download:false});text=await blob.blob.text()}
38
40
  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
41
  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)}