@cwellt_software/cwellt-reactjs-lib 1.3.2 → 1.3.5

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 (41) hide show
  1. package/dist/index.cjs.js +336 -169
  2. package/dist/index.css +2 -2
  3. package/dist/index.d.ts +61 -7
  4. package/dist/index.es.js +336 -169
  5. package/dist/src/common/functions/colorManipulation.d.ts.map +1 -1
  6. package/dist/src/common/functions/useSingleAndDoubleClicks.d.ts.map +1 -1
  7. package/dist/src/components/control/choice/multi-filter/CwMultiFilter.d.ts +6 -0
  8. package/dist/src/components/control/choice/multi-filter/CwMultiFilter.d.ts.map +1 -1
  9. package/dist/src/components/control/input/file/CwFileUploadMultiple.d.ts +16 -0
  10. package/dist/src/components/control/input/file/CwFileUploadMultiple.d.ts.map +1 -1
  11. package/dist/src/components/control/input/new-dates/CwDatePicker.d.ts +10 -1
  12. package/dist/src/components/control/input/new-dates/CwDatePicker.d.ts.map +1 -1
  13. package/dist/src/components/control/input/new-dates/CwDateTimePicker.d.ts +6 -1
  14. package/dist/src/components/control/input/new-dates/CwDateTimePicker.d.ts.map +1 -1
  15. package/dist/src/components/control/input/new-dates/CwDateTimePickerCompact.d.ts +8 -1
  16. package/dist/src/components/control/input/new-dates/CwDateTimePickerCompact.d.ts.map +1 -1
  17. package/dist/src/components/custom/scheduler-new/presentation/NewScheduler.d.ts +2 -0
  18. package/dist/src/components/custom/scheduler-new/presentation/NewScheduler.d.ts.map +1 -1
  19. package/dist/src/components/custom/scheduler-new/presentation/components/row/IndicatorRow.d.ts +18 -0
  20. package/dist/src/components/custom/scheduler-new/presentation/components/row/IndicatorRow.d.ts.map +1 -0
  21. package/dist/src/components/custom/scheduler-new/presentation/components/row/SchedulerRow.d.ts +2 -0
  22. package/dist/src/components/custom/scheduler-new/presentation/components/row/SchedulerRow.d.ts.map +1 -1
  23. package/dist/src/components/custom/super-scheduler/SuperScheduler.d.ts +2 -0
  24. package/dist/src/components/custom/super-scheduler/SuperScheduler.d.ts.map +1 -1
  25. package/dist/src/components/display/data/table/CwTable.d.ts +2 -1
  26. package/dist/src/components/display/data/table/CwTable.d.ts.map +1 -1
  27. package/dist/src/components/display/text/message/CwMessage.d.ts +3 -2
  28. package/dist/src/components/display/text/message/CwMessage.d.ts.map +1 -1
  29. package/dist/src/index.d.ts +1 -0
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/main.d.ts +0 -1
  32. package/dist/src/main.d.ts.map +1 -1
  33. package/dist/src/playground/PlaygroundApp.d.ts +4 -0
  34. package/dist/src/playground/PlaygroundApp.d.ts.map +1 -0
  35. package/dist/src/playground/pages/ColorContrastPage.d.ts +3 -0
  36. package/dist/src/playground/pages/ColorContrastPage.d.ts.map +1 -0
  37. package/dist/src/playground/pages/DatePickerLocalePage.d.ts +3 -0
  38. package/dist/src/playground/pages/DatePickerLocalePage.d.ts.map +1 -0
  39. package/dist/src/playground/pages/TablePaginationPage.d.ts +3 -0
  40. package/dist/src/playground/pages/TablePaginationPage.d.ts.map +1 -0
  41. package/package.json +2 -2
package/dist/index.es.js CHANGED
@@ -105,36 +105,60 @@ function getHSLColor(color, alpha = 1) {
105
105
  const hsl = colorToHSL(color);
106
106
  return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${alpha})`;
107
107
  }
108
+ function hslToLinearRgb(h, s, l) {
109
+ const sNorm = s / 100;
110
+ const lNorm = l / 100;
111
+ const a = sNorm * Math.min(lNorm, 1 - lNorm);
112
+ const f = (n) => {
113
+ const k = (n + h / 30) % 12;
114
+ return lNorm - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)));
115
+ };
116
+ return [f(0), f(8), f(4)];
117
+ }
118
+ function srgbLuminance(r, g, b) {
119
+ const lin = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
120
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
121
+ }
122
+ function wcagContrastRatio(lum1, lum2) {
123
+ const lighter = Math.max(lum1, lum2);
124
+ const darker = Math.min(lum1, lum2);
125
+ return (lighter + 0.05) / (darker + 0.05);
126
+ }
108
127
  function getContrastColor(color) {
109
- if (color === null) {
128
+ if (color === null)
110
129
  return '#000000';
111
- }
112
130
  const hsl = colorToHSL(color);
113
- // Saturated greenyellow and cyan tones need lower luminance threshold (40-35)
114
- // Saturated mediumblue and blueviolet tones need higher luminance threshold (55-60)
115
- // Saturated orangered tones need lower luminance threshold (45)
116
- const isYellowGreen = (hsl.h >= 45 && hsl.h <= 180);
117
- const isBlueViolet = (hsl.h >= 210 && hsl.h <= 300);
118
- const isRedOrange = (hsl.h >= 0 && hsl.h <= 30) || (hsl.h >= 330 && hsl.h <= 360);
119
- let threshold = 50;
120
- if (isYellowGreen) {
121
- threshold = 40;
122
- if (hsl.s >= 70) {
123
- threshold = 35;
124
- }
125
- }
126
- else if (isBlueViolet) {
127
- threshold = 55;
128
- if (hsl.s >= 70) {
129
- threshold = 60;
130
- }
131
- }
132
- else if (isRedOrange && hsl.s >= 80) {
133
- threshold = 45;
134
- }
135
- const contrastL = hsl.l >= threshold ? 20 : 90;
136
131
  const contrastS = Math.min(hsl.s, 90);
137
- return `hsl(${hsl.h}, ${contrastS}%, ${contrastL}%)`;
132
+ const bgRgb = hslToLinearRgb(hsl.h, hsl.s, hsl.l);
133
+ const bgLum = srgbLuminance(...bgRgb);
134
+ const darkL = 15;
135
+ const lightL = 90;
136
+ const darkLum = srgbLuminance(...hslToLinearRgb(hsl.h, contrastS, darkL));
137
+ const lightLum = srgbLuminance(...hslToLinearRgb(hsl.h, contrastS, lightL));
138
+ const darkRatio = wcagContrastRatio(bgLum, darkLum);
139
+ const lightRatio = wcagContrastRatio(bgLum, lightLum);
140
+ // On saturated backgrounds, dark-tinted text blends visually with the
141
+ // vibrant color even when math says it has better contrast.
142
+ // Prefer light text unless dark has an overwhelmingly higher ratio.
143
+ const saturatedBias = hsl.s >= 30 && bgLum < 0.4;
144
+ // Mid-grays (#777–#999): dark text "sinks" into the background while
145
+ // white text pops, even though dark wins on WCAG ratio.
146
+ const midGrayBias = hsl.s < 15 && bgLum >= 0.18 && bgLum <= 0.35;
147
+ const goLight = saturatedBias
148
+ ? darkRatio < lightRatio * 3
149
+ : midGrayBias || lightRatio >= darkRatio;
150
+ let targetL = goLight ? lightL : darkL;
151
+ let bestRatio = goLight ? lightRatio : darkRatio;
152
+ if (bestRatio < 4.5) {
153
+ const step = goLight ? 5 : -5;
154
+ const limit = goLight ? 95 : 0;
155
+ while (bestRatio < 4.5 && targetL !== limit) {
156
+ targetL = goLight ? Math.min(targetL + step, limit) : Math.max(targetL + step, limit);
157
+ const candidateLum = srgbLuminance(...hslToLinearRgb(hsl.h, contrastS, targetL));
158
+ bestRatio = wcagContrastRatio(bgLum, candidateLum);
159
+ }
160
+ }
161
+ return `hsl(${hsl.h}, ${contrastS}%, ${targetL}%)`;
138
162
  }
139
163
 
140
164
  const SVG_ICONS = {
@@ -229,7 +253,7 @@ const CwMessage = props => {
229
253
  }, props.duration ?? CW_DEFAULT_MESSAGE_DURATION);
230
254
  return () => clearTimeout(timer);
231
255
  }, [props]);
232
- return (jsxs("div", { className: "cw-message", "data-message-type": Object.keys(CwMessageType).find(key => CwMessageType[key] === props.messageType), children: [props.messageType && jsx(CwIcon, { iconId: props.messageType.toString(), size: "large" }), props.message] }));
256
+ return (jsxs("div", { className: "cw-message", "data-message-type": Object.keys(CwMessageType).find(key => CwMessageType[key] === props.messageType), onClick: props.onClick, style: props.onClick ? { cursor: "pointer" } : undefined, children: [props.messageType && jsx(CwIcon, { iconId: props.messageType.toString(), size: "large" }), props.message] }));
233
257
  };
234
258
  /**
235
259
  * Hook for displaying inline messages within specific components.
@@ -292,11 +316,11 @@ class CwMessageManager {
292
316
  document.body.prepend(this.messageWrapper);
293
317
  this.root = createRoot(this.messageWrapper); // Create a root at the messageWrapper
294
318
  }
295
- showMessage(message, type, duration) {
319
+ showMessage(message, type, duration, onClick) {
296
320
  const msg = document.createElement("div");
297
321
  this.messageWrapper?.prepend(msg);
298
322
  const msgRoot = createRoot(msg); // Create a root for the new message
299
- msgRoot.render(jsx(CwMessage, { message: message, messageType: type, duration: duration, onClose: () => this.closeMessage(msgRoot) }));
323
+ msgRoot.render(jsx(CwMessage, { message: message, messageType: type, duration: duration, onClick: onClick, onClose: () => this.closeMessage(msgRoot) }));
300
324
  }
301
325
  closeMessage(msgRoot) {
302
326
  msgRoot.unmount(); // Unmount the message root
@@ -318,8 +342,8 @@ class CwMessageManager {
318
342
  *
319
343
  * @note For inline messages within components, use `CwNote` or `useCwMessage` hook instead
320
344
  */
321
- function CwDisplayMessage(message, type, duration) {
322
- CwMessageManager.getInstance().showMessage(message, type, duration);
345
+ function CwDisplayMessage(message, type, duration, onClick) {
346
+ CwMessageManager.getInstance().showMessage(message, type, duration, onClick);
323
347
  }
324
348
 
325
349
  /**
@@ -1844,7 +1868,7 @@ function CwAccordionContainer(CwelltAccordionContainerProps) {
1844
1868
  *
1845
1869
  * @returns React component
1846
1870
  */
1847
- function CwTable({ columns, data, pagination = false, pageSizeOptions = [5, 10, 20, 50], expandedRowRender, onExpand, className, classNameRow, style, classNameContainer, id, textNoData = "No data available at the moment", rowKey = "key", loading = false, scrollHeight, stickyHeader = false, rowSelection }) {
1871
+ function CwTable({ columns, data, pagination = false, pageSizeOptions = [5, 10, 20, 50], expandedRowRender, onExpand, className, classNameRow, style, classNameContainer, id, textNoData = "No data available at the moment", rowKey = "key", loading = false, scrollHeight, stickyHeader = false, pageLabel = "page", rowSelection }) {
1848
1872
  const [currentPage, setCurrentPage] = useState(1);
1849
1873
  const [expandedRowKey, setExpandedRowKey] = useState(null);
1850
1874
  const [sortConfig, setSortConfig] = useState({
@@ -2029,16 +2053,16 @@ function CwTable({ columns, data, pagination = false, pageSizeOptions = [5, 10,
2029
2053
  ? "sortable"
2030
2054
  : sortConfig.direction === "asc"
2031
2055
  ? "sortable-asc"
2032
- : "sortable-desc" }))] }), jsx("span", { onMouseDown: (e) => startResize(e, col.key), className: "th-column-resizer" })] }, col.key)))] }) }), jsx("tbody", { children: renderTableBody() })] }) }), pagination && data.length > pageSizeOptions[0] && (jsxs("footer", { className: "cw-table-pagination", children: [jsx("button", { onClick: () => handlePageChange(1), disabled: currentPage === 1 || totalPages === 1, className: "cw-button-icon cwi-chevron-left-double", title: "First" }), jsx("button", { onClick: () => handlePageChange(currentPage - 1), disabled: currentPage === 1 || totalPages === 1, className: "cw-button-icon cwi-chevron-left", title: "Previous" }), jsx("input", { type: "text", inputMode: "numeric", value: currentPage, onChange: (e) => {
2033
- const value = parseInt(e.target.value, 10);
2034
- if (!isNaN(value))
2035
- handlePageChange(value);
2036
- }, onBlur: (e) => {
2037
- const value = parseInt(e.target.value, 10);
2038
- if (isNaN(value) || value < 1 || value > totalPages) {
2039
- handlePageChange(1);
2040
- }
2041
- }, min: 1, max: totalPages }), jsxs("span", { children: ["of ", totalPages] }), jsx("button", { onClick: () => handlePageChange(currentPage + 1), disabled: currentPage === totalPages || totalPages === 1, className: "cw-button-icon cwi-chevron-right", title: "Next" }), jsx("button", { onClick: () => handlePageChange(totalPages), disabled: currentPage === totalPages || totalPages === 1, className: "cw-button-icon cwi-chevron-right-double", title: "Last" }), jsx("select", { value: localItemsPerPage, onChange: handleItemsPerPageChange, children: pageSizeOptions.map(size => (jsxs("option", { value: size, children: [size, " / page"] }, size))) })] }))] }));
2056
+ : "sortable-desc" }))] }), jsx("span", { onMouseDown: (e) => startResize(e, col.key), className: "th-column-resizer" })] }, col.key)))] }) }), jsx("tbody", { children: renderTableBody() })] }) }), pagination && data.length > pageSizeOptions[0] && (jsxs("footer", { className: "cw-table-pagination", children: [jsxs("div", { className: "cw-table-pagination-size", children: [jsx(CwIcon, { iconId: "list" }), jsx("select", { value: localItemsPerPage, onChange: handleItemsPerPageChange, children: pageSizeOptions.map(size => (jsx("option", { value: size, children: pageLabel ? `${size} / ${pageLabel}` : size }, size))) })] }), jsxs("div", { className: "cw-table-pagination-nav", children: [jsx(CwButton, { onClick: () => handlePageChange(1), disabled: currentPage === 1 || totalPages === 1, variant: "icon", icon: "chevron-left-double" }), jsx(CwButton, { onClick: () => handlePageChange(currentPage - 1), disabled: currentPage === 1 || totalPages === 1, variant: "icon", icon: "chevron-left" }), jsx("input", { type: "text", inputMode: "numeric", value: currentPage, onChange: (e) => {
2057
+ const value = parseInt(e.target.value, 10);
2058
+ if (!isNaN(value))
2059
+ handlePageChange(value);
2060
+ }, onBlur: (e) => {
2061
+ const value = parseInt(e.target.value, 10);
2062
+ if (isNaN(value) || value < 1 || value > totalPages) {
2063
+ handlePageChange(1);
2064
+ }
2065
+ }, min: 1, max: totalPages }), jsx("span", { children: "/" }), jsx("span", { children: totalPages }), jsx(CwButton, { onClick: () => handlePageChange(currentPage + 1), disabled: currentPage === totalPages || totalPages === 1, variant: "icon", icon: "chevron-right" }), jsx(CwButton, { onClick: () => handlePageChange(totalPages), disabled: currentPage === totalPages || totalPages === 1, variant: "icon", icon: "chevron-right-double" })] })] }))] }));
2042
2066
  }
2043
2067
 
2044
2068
  var styles$i = {"dropIndicator":"cw-sortable-table-module__dropIndicator__ov-Jz","dragging":"cw-sortable-table-module__dragging__MrLrz","skeletonRow":"cw-sortable-table-module__skeletonRow__vyD0M","skeleton":"cw-sortable-table-module__skeleton__QGXAD","saveBar":"cw-sortable-table-module__saveBar__3OdoZ cw-flex-row cw-align-center-center cw-gap-small"};
@@ -2795,10 +2819,26 @@ function CwFileUpload(fileUploadProps) {
2795
2819
 
2796
2820
  var styles$e = {"fileUploadContainer":"cw-file-upload-multiple-module__fileUploadContainer__liEc1","hiddenInput":"cw-file-upload-multiple-module__hiddenInput__TZBBI","uploadArea":"cw-file-upload-multiple-module__uploadArea__DdOhs","uploadAreaDisabled":"cw-file-upload-multiple-module__uploadAreaDisabled__VWeFX","uploadTitle":"cw-file-upload-multiple-module__uploadTitle__gjRk8","uploadSubtitle":"cw-file-upload-multiple-module__uploadSubtitle__Z0S5t","filesContainer":"cw-file-upload-multiple-module__filesContainer__g44PY","fileItem":"cw-file-upload-multiple-module__fileItem__w27Dg","fileIcon":"cw-file-upload-multiple-module__fileIcon__iJJUX","fileExtension":"cw-file-upload-multiple-module__fileExtension__vOuHv","fileInfo":"cw-file-upload-multiple-module__fileInfo__R5ZTv","fileName":"cw-file-upload-multiple-module__fileName__DjepK","fileSize":"cw-file-upload-multiple-module__fileSize__b8GSm","smallButton":"cw-file-upload-multiple-module__smallButton__siUAh"};
2797
2821
 
2822
+ const DEFAULT_LABELS = {
2823
+ uploadDisabled: 'Upload disabled',
2824
+ clickToUpload: 'Click to upload or drag and drop',
2825
+ acceptedFiles: (accept) => `Accepted files: ${accept}`,
2826
+ singleFileOnly: '(Single file only)',
2827
+ allTypesAccepted: 'All file types accepted',
2828
+ changeFile: 'Change File',
2829
+ clearAll: 'Clear all',
2830
+ addMoreFiles: 'Add More Files',
2831
+ filesSelected: (count) => `${count} file${count !== 1 ? 's' : ''} selected`,
2832
+ fileTooLarge: (name, maxSize) => `File "${name}" is too large. Maximum size allowed is ${maxSize}MB.`,
2833
+ invalidFormat: (name, accepted) => `File "${name}" has an invalid format. Allowed formats: ${accepted}`,
2834
+ invalidMimeType: (name, types) => `File "${name}" has an invalid MIME type. Allowed types: ${types}`,
2835
+ alreadySelected: (name) => `File "${name}" is already selected.`,
2836
+ };
2798
2837
  function CwFileUploadMultiple(fileUploadProps) {
2799
2838
  const fileInputRef = useRef(null);
2800
2839
  const [selectedFiles, setSelectedFiles] = useState([]);
2801
2840
  const [existingFile, setExistingFile] = useState(fileUploadProps.initialFileName);
2841
+ const labels = { ...DEFAULT_LABELS, ...fileUploadProps.labels };
2802
2842
  React__default.useEffect(() => {
2803
2843
  setExistingFile(fileUploadProps.initialFileName);
2804
2844
  }, [fileUploadProps.initialFileName]);
@@ -2812,7 +2852,7 @@ function CwFileUploadMultiple(fileUploadProps) {
2812
2852
  if (fileSizeInMB > fileUploadProps.maxFileSize) {
2813
2853
  return {
2814
2854
  isValid: false,
2815
- error: `File "${file.name}" is too large. Maximum size allowed is ${fileUploadProps.maxFileSize}MB.`
2855
+ error: labels.fileTooLarge(file.name, fileUploadProps.maxFileSize)
2816
2856
  };
2817
2857
  }
2818
2858
  }
@@ -2829,7 +2869,7 @@ function CwFileUploadMultiple(fileUploadProps) {
2829
2869
  if (!isExtensionValid) {
2830
2870
  return {
2831
2871
  isValid: false,
2832
- error: `File "${file.name}" has an invalid format. Allowed formats: ${fileUploadProps.accept}`
2872
+ error: labels.invalidFormat(file.name, fileUploadProps.accept)
2833
2873
  };
2834
2874
  }
2835
2875
  }
@@ -2839,7 +2879,7 @@ function CwFileUploadMultiple(fileUploadProps) {
2839
2879
  if (!isMimeTypeValid) {
2840
2880
  return {
2841
2881
  isValid: false,
2842
- error: `File "${file.name}" has an invalid MIME type. Allowed types: ${fileUploadProps.allowedTypes.join(', ')}`
2882
+ error: labels.invalidMimeType(file.name, fileUploadProps.allowedTypes.join(', '))
2843
2883
  };
2844
2884
  }
2845
2885
  }
@@ -2853,7 +2893,7 @@ function CwFileUploadMultiple(fileUploadProps) {
2853
2893
  // Check if the file already exists (by name and size)
2854
2894
  const isDuplicate = existingFiles.some(existingFile => existingFile.name === file.name && existingFile.size === file.size);
2855
2895
  if (isDuplicate) {
2856
- errors.push(`File "${file.name}" is already selected.`);
2896
+ errors.push(labels.alreadySelected(file.name));
2857
2897
  continue;
2858
2898
  }
2859
2899
  const validation = validateFile(file);
@@ -2944,12 +2984,12 @@ function CwFileUploadMultiple(fileUploadProps) {
2944
2984
  }
2945
2985
  }
2946
2986
  };
2947
- return (jsxs("div", { className: `${styles$e.fileUploadContainer} ${fileUploadProps.className}`, children: [jsx("input", { ref: fileInputRef, type: "file", name: fileUploadProps.name, accept: fileUploadProps.accept, multiple: fileUploadProps.multiple, onChange: handleFileSelectInternal, disabled: fileUploadProps.disabled, "aria-label": "files", className: styles$e.hiddenInput }), selectedFiles.length === 0 && !existingFile ? (jsxs("div", { className: `${styles$e.uploadArea} ${fileUploadProps.disabled ? styles$e.uploadAreaDisabled : ''}`, onDragOver: handleDragOver, onDrop: handleDrop, onClick: !fileUploadProps.disabled ? handleButtonClick : undefined, children: [jsx(CwIcon, { iconId: "upload" }), jsx("p", { className: `${styles$e.uploadTitle}`, children: fileUploadProps.disabled ? 'Upload disabled' : 'Click to upload or drag and drop' }), jsxs("p", { className: `${styles$e.uploadSubtitle}`, children: [fileUploadProps.accept ? `Accepted files: ${fileUploadProps.accept}` : 'All file types accepted', !fileUploadProps.multiple && ' (Single file only)'] })] })) : selectedFiles.length === 0 && existingFile ? (jsxs("div", { className: styles$e.filesContainer, children: [jsxs("div", { className: styles$e.fileItem, children: [jsxs("div", { className: styles$e.fileIcon, children: [jsx(CwIcon, { iconId: "page" }), jsx("span", { className: styles$e.fileExtension, children: getFileExtension(existingFile) })] }), jsx("div", { className: styles$e.fileInfo, children: jsx("p", { className: styles$e.fileName, children: existingFile }) }), jsx(CwButton, { variant: "icon", icon: "close", color: "neutral", onClick: () => {
2987
+ return (jsxs("div", { className: `${styles$e.fileUploadContainer} ${fileUploadProps.className}`, children: [jsx("input", { ref: fileInputRef, type: "file", name: fileUploadProps.name, accept: fileUploadProps.accept, multiple: fileUploadProps.multiple, onChange: handleFileSelectInternal, disabled: fileUploadProps.disabled, "aria-label": "files", className: styles$e.hiddenInput }), selectedFiles.length === 0 && !existingFile ? (jsxs("div", { className: `${styles$e.uploadArea} ${fileUploadProps.disabled ? styles$e.uploadAreaDisabled : ''}`, onDragOver: handleDragOver, onDrop: handleDrop, onClick: !fileUploadProps.disabled ? handleButtonClick : undefined, children: [jsx(CwIcon, { iconId: "upload" }), jsx("p", { className: `${styles$e.uploadTitle}`, children: fileUploadProps.disabled ? labels.uploadDisabled : labels.clickToUpload }), jsxs("p", { className: `${styles$e.uploadSubtitle}`, children: [fileUploadProps.accept ? labels.acceptedFiles(fileUploadProps.accept) : labels.allTypesAccepted, !fileUploadProps.multiple && ` ${labels.singleFileOnly}`] })] })) : selectedFiles.length === 0 && existingFile ? (jsxs("div", { className: styles$e.filesContainer, children: [jsxs("div", { className: styles$e.fileItem, children: [jsxs("div", { className: styles$e.fileIcon, children: [jsx(CwIcon, { iconId: "page" }), jsx("span", { className: styles$e.fileExtension, children: getFileExtension(existingFile) })] }), jsx("div", { className: styles$e.fileInfo, children: jsx("p", { className: styles$e.fileName, children: existingFile }) }), jsx(CwButton, { variant: "icon", icon: "close", color: "neutral", onClick: () => {
2948
2988
  setExistingFile(undefined);
2949
2989
  if (fileUploadProps.onSelect) {
2950
2990
  fileUploadProps.onSelect(null);
2951
2991
  }
2952
- }, disabled: fileUploadProps.disabled, className: styles$e.smallButton })] }), jsx(CwButton, { text: "Change File", icon: "refresh", onClick: handleButtonClick, disabled: fileUploadProps.disabled })] })) : (jsxs("div", { className: styles$e.filesContainer, children: [jsxs("div", { className: "cw-flex-row cw-align-between-center", children: [jsxs("small", { className: styles$e.filesCount, children: [selectedFiles.length, " file", selectedFiles.length !== 1 ? 's' : '', " selected"] }), jsx(CwButton, { onClick: removeAllFiles, disabled: fileUploadProps.disabled, color: "danger", variant: "outline", icon: "delete", text: "Clear all" })] }), selectedFiles.map((file, index) => (jsxs("div", { className: styles$e.fileItem, children: [jsxs("div", { className: styles$e.fileIcon, children: [jsx(CwIcon, { iconId: "page" }), jsx("span", { className: styles$e.fileExtension, children: getFileExtension(file.name) })] }), jsxs("div", { className: styles$e.fileInfo, children: [jsx("p", { className: styles$e.fileName, children: file.name }), jsxs("p", { className: styles$e.fileSize, children: [(file.size / 1024).toFixed(1), " KB"] })] }), jsx(CwButton, { variant: "icon", icon: "close", color: "neutral", onClick: () => removeFile(index), className: styles$e.smallButton })] }, index))), fileUploadProps.multiple && (jsx(CwButton, { text: "Add More Files", icon: "plus", variant: "outline", onClick: handleButtonClick, disabled: fileUploadProps.disabled })), !fileUploadProps.multiple && (jsx(CwButton, { text: "Change File", icon: "refresh", onClick: handleButtonClick, disabled: fileUploadProps.disabled }))] }))] }));
2992
+ }, disabled: fileUploadProps.disabled, className: styles$e.smallButton })] }), jsx(CwButton, { text: labels.changeFile, icon: "refresh", onClick: handleButtonClick, disabled: fileUploadProps.disabled })] })) : (jsxs("div", { className: styles$e.filesContainer, children: [jsxs("div", { className: "cw-flex-row cw-align-between-center", children: [jsx("small", { className: styles$e.filesCount, children: labels.filesSelected(selectedFiles.length) }), jsx(CwButton, { onClick: removeAllFiles, disabled: fileUploadProps.disabled, color: "danger", variant: "outline", icon: "delete", text: labels.clearAll })] }), selectedFiles.map((file, index) => (jsxs("div", { className: styles$e.fileItem, children: [jsxs("div", { className: styles$e.fileIcon, children: [jsx(CwIcon, { iconId: "page" }), jsx("span", { className: styles$e.fileExtension, children: getFileExtension(file.name) })] }), jsxs("div", { className: styles$e.fileInfo, children: [jsx("p", { className: styles$e.fileName, children: file.name }), jsxs("p", { className: styles$e.fileSize, children: [(file.size / 1024).toFixed(1), " KB"] })] }), jsx(CwButton, { variant: "icon", icon: "close", color: "neutral", onClick: () => removeFile(index), className: styles$e.smallButton })] }, index))), fileUploadProps.multiple && (jsx(CwButton, { text: labels.addMoreFiles, icon: "plus", variant: "outline", onClick: handleButtonClick, disabled: fileUploadProps.disabled })), !fileUploadProps.multiple && (jsx(CwButton, { text: labels.changeFile, icon: "refresh", onClick: handleButtonClick, disabled: fileUploadProps.disabled }))] }))] }));
2953
2993
  }
2954
2994
 
2955
2995
  function CwInput(CwInputProps) {
@@ -4309,7 +4349,7 @@ const CwMultiFilterTag = props => {
4309
4349
  } : undefined }) }));
4310
4350
  };
4311
4351
 
4312
- var styles$7 = {"cw-multi-filter-catalog-container":"cw-multi-filter-module__cw-multi-filter-catalog-container__S3nsq","cw-multi-filter":"cw-multi-filter-module__cw-multi-filter__zipBK","cw-multi-filter-search":"cw-multi-filter-module__cw-multi-filter-search__eyHr0"};
4352
+ var styles$7 = {"cw-multi-filter-catalog-container":"cw-multi-filter-module__cw-multi-filter-catalog-container__S3nsq","cw-multi-filter":"cw-multi-filter-module__cw-multi-filter__zipBK","category-selected":"cw-multi-filter-module__category-selected__eYbes","cw-multi-filter-search":"cw-multi-filter-module__cw-multi-filter-search__eyHr0"};
4313
4353
 
4314
4354
  /**
4315
4355
  * A multiple filter selector, a MULTI-SELECT even. Allows users to select and filter items based on tags.
@@ -4450,7 +4490,7 @@ var styles$7 = {"cw-multi-filter-catalog-container":"cw-multi-filter-module__cw-
4450
4490
  * @param {CwMultiFilterProps} props
4451
4491
  * @returns Set the `onChange` callback to a function to check for changes in the selected filters
4452
4492
  */
4453
- const CwMultiFilter = ({ allTags, id, onChangeSelectedTags, selectedTags, style }) => {
4493
+ const CwMultiFilter = ({ allTags, id, onChangeSelectedTags, selectedTags, placeholder = "Write to filter", allCategoriesLabel = "ALL CATEGORIES", className, style }) => {
4454
4494
  const [filteredTags, setFilteredTags] = useState(new Set());
4455
4495
  const [inputTextValue, setInputTextValue] = useState("");
4456
4496
  const [isPanelOpen, setIsPanelOpen] = useState(false);
@@ -4552,9 +4592,9 @@ const CwMultiFilter = ({ allTags, id, onChangeSelectedTags, selectedTags, style
4552
4592
  inputRef.current?.focus();
4553
4593
  }
4554
4594
  };
4555
- return (jsxs("form", { ref: componentRef, id: id, className: styles$7["cw-multi-filter"], style: style, onSubmit: (e) => {
4595
+ return (jsxs("form", { ref: componentRef, id: id, className: `${styles$7["cw-multi-filter"]}${className ? ` ${className}` : ""}`, style: style, onSubmit: (e) => {
4556
4596
  e.preventDefault();
4557
- }, children: [jsxs("div", { onClick: handleDivClick, className: styles$7["cw-multi-filter-search"], style: isPanelOpen ? { outline: "1px solid var(--cw-color-primary)", outlineOffset: "-2px" } : {}, children: [jsxs("ul", { id: id + "_selected_filters", children: [Array.from(selectedTags).map(tag => (createElement(CwMultiFilterTag, { ...tag, key: tag.ID, Selectable: false, Removable: true, OnRemove: () => removeTag(tag.ID) }))), jsx("input", { type: "text", id: id + "_input", ref: inputRef, value: inputTextValue, spellCheck: false, onFocus: () => setIsPanelOpen(true), onChange: e => handleInputText(e.target.value), autoComplete: "off", placeholder: "Write to filter", onKeyDown: e => {
4597
+ }, children: [jsxs("div", { onClick: handleDivClick, className: styles$7["cw-multi-filter-search"], style: isPanelOpen ? { outline: "1px solid var(--cw-color-primary)", outlineOffset: "-2px" } : {}, children: [jsxs("ul", { id: id + "_selected_filters", children: [Array.from(selectedTags).map(tag => (createElement(CwMultiFilterTag, { ...tag, key: tag.ID, Selectable: false, Removable: true, OnRemove: () => removeTag(tag.ID) }))), jsx("input", { type: "text", id: id + "_input", ref: inputRef, value: inputTextValue, spellCheck: false, onFocus: () => setIsPanelOpen(true), onChange: e => handleInputText(e.target.value), autoComplete: "off", placeholder: placeholder, onKeyDown: e => {
4558
4598
  switch (e.key) {
4559
4599
  case "Enter":
4560
4600
  case "Tab": {
@@ -4584,15 +4624,7 @@ const CwMultiFilter = ({ allTags, id, onChangeSelectedTags, selectedTags, style
4584
4624
  } })] }), selectedTags.size > 0 ? (jsx("input", { type: "reset", value: "\u00D7", onClick: e => {
4585
4625
  e.preventDefault();
4586
4626
  onChangeSelectedTags(new Set());
4587
- } })) : null] }), jsxs("section", { className: styles$7["cw-multi-filter-catalog-container"], "data-display-none": !isPanelOpen, children: [jsxs("nav", { children: [jsx("button", { style: selectedCategory === "All"
4588
- ? {
4589
- backgroundColor: "var(--cw-color-primary-container)",
4590
- color: "var(--cw-color-primary)",
4591
- outline: "2px solid var(--cw-color-primary)",
4592
- outlineOffset: "-2px",
4593
- fontWeight: 400
4594
- }
4595
- : {}, onClick: () => handleClickCategory("All"), children: "SEARCH IN ALL CATEGORIES" }), Array.from(categoriesMappedToTags().keys()).map(category => (jsx("button", { style: selectedCategory === category
4627
+ } })) : null] }), jsxs("section", { className: styles$7["cw-multi-filter-catalog-container"], "data-display-none": !isPanelOpen, children: [jsxs("nav", { children: [jsx("button", { className: selectedCategory === "All" ? styles$7["category-selected"] : undefined, onClick: () => handleClickCategory("All"), children: allCategoriesLabel }), Array.from(categoriesMappedToTags().keys()).map(category => (jsx("button", { style: selectedCategory === category
4596
4628
  ? {
4597
4629
  backgroundColor: getColor(category).primary,
4598
4630
  color: getColor(category).onPrimary,
@@ -5142,10 +5174,13 @@ function usePickerPopup({ anchorRef, isOpen, onClose, position = "left-bottom" }
5142
5174
  return { popupRef, popupStyle: style, renderPopup };
5143
5175
  }
5144
5176
 
5145
- function CwDatePicker({ value, onChange, minDate, maxDate, disabledDates, disabledMatcher, defaultMonth, labelProps, alignProps, placeholder = "Select a date", displayFormat = "dd.MM.yyyy", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, }) {
5177
+ function CwDatePicker({ value, onChange, minDate, maxDate, disabledDates, disabledMatcher, defaultMonth, labelProps, alignProps, placeholder = "Select a date", displayFormat = "dd.MM.yyyy", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, locale = enGB, todayLabel = "Today", feedback, }) {
5146
5178
  const [isOpen, setIsOpen] = useState(false);
5147
5179
  const [inputValue, setInputValue] = useState("");
5148
5180
  const containerRef = useRef(null);
5181
+ const feedbackMessages = feedback
5182
+ ? Array.isArray(feedback) ? feedback : [feedback]
5183
+ : [];
5149
5184
  const wrapperRef = useRef(null);
5150
5185
  const inputRef = useRef(null);
5151
5186
  const prevValueRef = useRef(undefined);
@@ -5313,11 +5348,11 @@ function CwDatePicker({ value, onChange, minDate, maxDate, disabledDates, disabl
5313
5348
  ...(maxDate ? [{ after: maxDate }] : []),
5314
5349
  ...(disabledMatcher ? [disabledMatcher] : []),
5315
5350
  ], [disabledDates, minDate, maxDate, disabledMatcher]);
5316
- return (jsx("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsx(DayPicker, { mode: "single", selected: value || undefined, defaultMonth: defaultMonth || value || undefined, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, modifiers: {
5317
- today: new Date(),
5318
- }, modifiersClassNames: {
5319
- today: "rdp-day-today",
5320
- } }), showTodayButton && (jsx("footer", { className: "cw-flex-row cw-align-right-center", children: jsx(CwButton, { type: "button", variant: "outline", onClick: handleTodayClick, text: "Today" }) }))] }))] })] }) }));
5351
+ return (jsxs("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: [jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsx(DayPicker, { mode: "single", selected: value || undefined, defaultMonth: defaultMonth || value || undefined, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, locale: locale, modifiers: {
5352
+ today: new Date(),
5353
+ }, modifiersClassNames: {
5354
+ today: "rdp-day-today",
5355
+ } }), showTodayButton && (jsx("footer", { className: "cw-flex-row cw-align-right-center", children: jsx(CwButton, { type: "button", variant: "outline", onClick: handleTodayClick, text: todayLabel }) }))] }))] })] }), feedbackMessages.map((feedbackItem, index) => (jsx("p", { className: "cw-input-info", "data-color": feedbackItem.type, children: feedbackItem.message }, index)))] }));
5321
5356
  }
5322
5357
 
5323
5358
  var rangeStyles = {"rangeWrapper":"cw-range-picker-module__rangeWrapper__1nIVs","rangePopup":"cw-range-picker-module__rangePopup__E5jd1","presetList":"cw-range-picker-module__presetList__INiLo"};
@@ -6165,7 +6200,7 @@ function combineDateTimeInOffset(date, hours, minutes, timezoneOffset) {
6165
6200
  return new Date(zdt.epochMilliseconds);
6166
6201
  }
6167
6202
 
6168
- function CwDateTimePicker({ value, onChange, minDateTime, maxDateTime, disabledDates, disabledMatcher, timeInterval = 15, minTime, maxTime, labelProps, alignProps, datePlaceholder = "dd.MM.yyyy", timePlaceholder = "HH:mm", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showNowButton = false, timezoneOffset, }) {
6203
+ function CwDateTimePicker({ value, onChange, minDateTime, maxDateTime, disabledDates, disabledMatcher, timeInterval = 15, minTime, maxTime, labelProps, alignProps, datePlaceholder = "dd.MM.yyyy", timePlaceholder = "HH:mm", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showNowButton = false, nowLabel = "Now", locale = enGB, timezoneOffset, }) {
6169
6204
  // ========================================
6170
6205
  // PROPS NORMALIZATION
6171
6206
  // ========================================
@@ -6405,12 +6440,12 @@ function CwDateTimePicker({ value, onChange, minDateTime, maxDateTime, disabledD
6405
6440
  const displayMaxDate = useMemo(() => normalizedMaxDateTime && timezoneOffset !== undefined
6406
6441
  ? getDateInOffset(normalizedMaxDateTime, timezoneOffset)
6407
6442
  : normalizedMaxDateTime, [normalizedMaxDateTime, timezoneOffset]);
6408
- return jsx("div", { className: `cw-datetimepicker cw-datetimepicker-separate ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { className: "cw-flex-row cw-align-left-center cw-gap-small", children: [jsx(CwDatePicker, { value: selectedDate, onChange: handleDateChange, minDate: displayMinDate, maxDate: displayMaxDate, disabledDates: disabledDates, disabledMatcher: disabledMatcher, placeholder: datePlaceholder, disabled: disabled, required: required, showClear: showClear, numberOfMonths: numberOfMonths, popupPosition: popupPosition }), jsx(CwTimePicker, { value: selectedTime, onChange: handleTimeChange, interval: timeInterval, minTime: timeRestrictions.minTime, maxTime: timeRestrictions.maxTime, placeholder: timePlaceholder, disabled: disabled, required: required, showClear: showClear, popupPosition: popupPosition }), showNowButton && (jsx(CwButton, { type: "button", variant: "outline", onClick: handleNowClick, disabled: disabled, title: "Set to current date and time", text: "Now" }))] })] }) });
6443
+ return jsx("div", { className: `cw-datetimepicker cw-datetimepicker-separate ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { className: "cw-flex-row cw-align-left-center cw-gap-small", children: [jsx(CwDatePicker, { value: selectedDate, onChange: handleDateChange, minDate: displayMinDate, maxDate: displayMaxDate, disabledDates: disabledDates, disabledMatcher: disabledMatcher, placeholder: datePlaceholder, disabled: disabled, required: required, showClear: showClear, numberOfMonths: numberOfMonths, popupPosition: popupPosition, locale: locale }), jsx(CwTimePicker, { value: selectedTime, onChange: handleTimeChange, interval: timeInterval, minTime: timeRestrictions.minTime, maxTime: timeRestrictions.maxTime, placeholder: timePlaceholder, disabled: disabled, required: required, showClear: showClear, popupPosition: popupPosition }), showNowButton && (jsx(CwButton, { type: "button", variant: "outline", onClick: handleNowClick, disabled: disabled, text: nowLabel }))] })] }) });
6409
6444
  }
6410
6445
 
6411
6446
  var compactStyles = {"compactPopup":"cw-datetime-compact-module__compactPopup__GiuNY","calendarWrapper":"cw-datetime-compact-module__calendarWrapper__P4Nlq","timeWrapper":"cw-datetime-compact-module__timeWrapper__uMe-A","compactTimeList":"cw-datetime-compact-module__compactTimeList__MzSQT"};
6412
6447
 
6413
- function CwDateTimePickerCompact({ value, onChange, minDateTime, maxDateTime, disabledDates, disabledMatcher, timeInterval = 15, minTime, maxTime, labelProps, alignProps, placeholder = "dd.mm.yyyy HH:mm", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, }) {
6448
+ function CwDateTimePickerCompact({ value, onChange, minDateTime, maxDateTime, disabledDates, disabledMatcher, timeInterval = 15, minTime, maxTime, labelProps, alignProps, placeholder = "dd.mm.yyyy HH:mm", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, nowLabel = "Now", timeLabel = "Time", locale = enGB, }) {
6414
6449
  const [isOpen, setIsOpen] = useState(false);
6415
6450
  const [inputValue, setInputValue] = useState("");
6416
6451
  const [selectedDate, setSelectedDate] = useState(value);
@@ -6641,11 +6676,11 @@ function CwDateTimePickerCompact({ value, onChange, minDateTime, maxDateTime, di
6641
6676
  ...(maxDateTime ? [{ after: maxDateTime }] : []),
6642
6677
  ...(disabledMatcher ? [disabledMatcher] : []),
6643
6678
  ], [disabledDates, minDateTime, maxDateTime, disabledMatcher]);
6644
- return (jsx("div", { ref: containerRef, className: `cw-datetimepicker ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required, maxLength: 16, style: { width: "24ch" } }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear datetime" })) : (jsx(CwIcon, { iconId: "calendar-time" })) }), renderPopup(jsxs("div", { ref: popupRef, className: `${styles$4.pickerPopup} ${compactStyles.compactPopup}`, style: popupStyle, children: [jsxs("div", { className: compactStyles.calendarWrapper, children: [jsx(DayPicker, { mode: "single", selected: selectedDate, onSelect: handleDaySelect, defaultMonth: selectedDate, disabled: disabledDays, numberOfMonths: numberOfMonths, modifiers: {
6679
+ return (jsx("div", { ref: containerRef, className: `cw-datetimepicker ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required, maxLength: 16 }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear datetime" })) : (jsx(CwIcon, { iconId: "calendar-time" })) }), renderPopup(jsxs("div", { ref: popupRef, className: `${styles$4.pickerPopup} ${compactStyles.compactPopup}`, style: popupStyle, children: [jsxs("div", { className: compactStyles.calendarWrapper, children: [jsx(DayPicker, { mode: "single", selected: selectedDate, onSelect: handleDaySelect, defaultMonth: selectedDate, disabled: disabledDays, numberOfMonths: numberOfMonths, locale: locale, modifiers: {
6645
6680
  today: new Date(),
6646
6681
  }, modifiersClassNames: {
6647
6682
  today: "rdp-day-today",
6648
- } }), showTodayButton && (jsx("footer", { className: "cw-flex-row cw-align-center-center", children: jsx(CwButton, { type: "button", variant: "outline", icon: "check-big", onClick: handleNowClick, text: "Now" }) }))] }), jsxs("div", { className: compactStyles.timeWrapper, children: [jsxs("header", { children: [jsx(CwIcon, { iconId: "clock", size: "medium" }), jsx("span", { children: "Time" })] }), jsx("div", { ref: timeListRef, className: `${timeStyles.timePickerList} ${compactStyles.compactTimeList}`, children: timeOptions.map((time) => {
6683
+ } }), showTodayButton && (jsx("footer", { className: "cw-flex-row cw-align-center-center", children: jsx(CwButton, { type: "button", variant: "outline", icon: "check-big", onClick: handleNowClick, text: nowLabel }) }))] }), jsxs("div", { className: compactStyles.timeWrapper, children: [jsxs("header", { children: [jsx(CwIcon, { iconId: "clock", size: "medium" }), jsx("span", { children: timeLabel })] }), jsx("div", { ref: timeListRef, className: `${timeStyles.timePickerList} ${compactStyles.compactTimeList}`, children: timeOptions.map((time) => {
6649
6684
  const isSelected = time === selectedTime;
6650
6685
  return (jsx("button", { type: "button", className: isSelected ? timeStyles.selected : "", onClick: () => handleTimeSelect(time), children: time }, time));
6651
6686
  }) })] })] }))] })] }) }));
@@ -7985,30 +8020,95 @@ const BackgroundEvent = ({ value, heightRem }) => {
7985
8020
  }, children: value.icons }) : null, jsx("span", { className: styles$2["scheduler-event-text"], children: value.name })] }) }) }) }) })) : null;
7986
8021
  };
7987
8022
 
8023
+ const eventIsVisible = (startDate, endDate, selectedDate, visibleDays) => {
8024
+ const schedulerEnd = Temporal.PlainDate.from({
8025
+ year: selectedDate.getFullYear(),
8026
+ month: selectedDate.getMonth() + 1,
8027
+ day: selectedDate.getDate(),
8028
+ }).add({ days: visibleDays });
8029
+ const schedulerEndDate = new Date(schedulerEnd.year, schedulerEnd.month - 1, schedulerEnd.day);
8030
+ const isBefore = selectedDate > startDate && selectedDate > endDate;
8031
+ const isAfter = schedulerEndDate < startDate && schedulerEndDate < endDate;
8032
+ return !isBefore && !isAfter;
8033
+ };
8034
+
8035
+ const hoursBetween = (date1, date2) => {
8036
+ const oneHourInMillis = 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
8037
+ const timeDiff = date2.getTime() - date1.getTime();
8038
+ return timeDiff / oneHourInMillis;
8039
+ };
8040
+ const getEventSizes = (schedulerDate, totalDays, startDate, endDate) => {
8041
+ if (!eventIsVisible(startDate, endDate, schedulerDate, totalDays)) {
8042
+ return {
8043
+ left: 0,
8044
+ width: 0,
8045
+ };
8046
+ }
8047
+ const totalHours = totalDays * 24;
8048
+ // const dateString = schedulerDate.toISOString().split('T')[0];
8049
+ //const schedulerDateAtZero = new Date(dateString)
8050
+ let startHours = hoursBetween(schedulerDate, startDate);
8051
+ let durationHours = hoursBetween(startDate, endDate);
8052
+ const startOutOfScheduler = startHours < 0;
8053
+ if (startOutOfScheduler) {
8054
+ durationHours += startHours;
8055
+ startHours = 0;
8056
+ }
8057
+ const left = (startHours / totalHours) * 100;
8058
+ // Minimum width equivalent to 15 minutes so zero/near-zero duration events stay visible
8059
+ const MIN_WIDTH_HOURS = 0.25;
8060
+ const minWidth = (MIN_WIDTH_HOURS * 100) / totalHours;
8061
+ const width = Math.max((durationHours * 100) / totalHours, minWidth);
8062
+ return {
8063
+ left,
8064
+ width,
8065
+ };
8066
+ };
8067
+
8068
+ const INDICATOR_HEIGHT_REM = 0.25;
8069
+ const IndicatorRow = ({ indicators, selectedDate, visibleDays }) => {
8070
+ return (jsx("div", { style: {
8071
+ position: "relative",
8072
+ height: `${INDICATOR_HEIGHT_REM}rem`,
8073
+ pointerEvents: "none",
8074
+ }, "data-name": "indicator-row", children: indicators.map((indicator) => {
8075
+ const { left, width } = getEventSizes(selectedDate, visibleDays, indicator.start, indicator.end);
8076
+ if (width <= 0)
8077
+ return null;
8078
+ const bar = (jsx("div", { style: {
8079
+ position: "absolute",
8080
+ left: `${left}%`,
8081
+ width: `${width}%`,
8082
+ height: `${INDICATOR_HEIGHT_REM}rem`,
8083
+ backgroundColor: indicator.color,
8084
+ borderRadius: "1px",
8085
+ pointerEvents: "auto",
8086
+ } }, indicator.id));
8087
+ if (indicator.tooltip) {
8088
+ return (jsx(CwTooltipNew, { content: indicator.tooltip, position: "bottom", dissapearsWhenHover: true, showDelay: 200, children: bar }, indicator.id));
8089
+ }
8090
+ return bar;
8091
+ }) }));
8092
+ };
8093
+
8094
+ // Fires onClick on the first click of a sequence (detail === 1) and
8095
+ // onDoubleClick on the native dblclick event. Subsequent clicks of a
8096
+ // double-click sequence (detail > 1) are suppressed so the same logical
8097
+ // interaction does not fire onClick twice. Selection therefore appears
8098
+ // within one paint frame instead of being delayed by an artificial timer.
7988
8099
  function useSingleAndDoubleClicks(onClick, onDoubleClick) {
7989
- const timer = useRef(null);
7990
- const cancelPendingClick = useCallback(() => {
7991
- if (timer.current) {
7992
- clearTimeout(timer.current);
7993
- timer.current = null;
7994
- }
7995
- }, [timer]);
7996
8100
  const handleClick = useCallback((e) => {
7997
- // We only cache the most recent click event, so cancel any pending clicks
7998
8101
  e.stopPropagation();
7999
8102
  e.preventDefault();
8000
- cancelPendingClick();
8001
- timer.current = setTimeout(() => {
8002
- timer.current = null;
8003
- onClick(e);
8004
- }, 500);
8005
- }, [timer, cancelPendingClick, onClick]);
8103
+ if (e.detail > 1)
8104
+ return;
8105
+ onClick(e);
8106
+ }, [onClick]);
8006
8107
  const handleDoubleClick = useCallback((e) => {
8007
8108
  e.stopPropagation();
8008
8109
  e.preventDefault();
8009
- cancelPendingClick();
8010
8110
  onDoubleClick(e);
8011
- }, [cancelPendingClick, onDoubleClick]);
8111
+ }, [onDoubleClick]);
8012
8112
  return { handleClick, handleDoubleClick };
8013
8113
  }
8014
8114
 
@@ -8182,7 +8282,7 @@ const SchedulerEvent = ({ value, heightRem, onEvent }) => {
8182
8282
 
8183
8283
  const SchedulerRow = memo((props) => {
8184
8284
  const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
8185
- const { events, backgroundEvents, rowHeader, contextMenuItems, RowTitleComp, EventComp, BackgroundEventComp, weekendLines, divisionLines, timeLinePercentage, selectedDate, visibleDays, onEvent, } = props;
8285
+ const { events, backgroundEvents, indicatorRows, rowHeader, contextMenuItems, RowTitleComp, EventComp, BackgroundEventComp, weekendLines, divisionLines, timeLinePercentage, selectedDate, visibleDays, onEvent, } = props;
8186
8286
  const internalRows = separateEventsToInnerRows(events);
8187
8287
  const schedulerDivRef = useRef(null);
8188
8288
  const { handleClick, handleDoubleClick } = useSingleAndDoubleClicks((e) => {
@@ -8239,7 +8339,7 @@ const SchedulerRow = memo((props) => {
8239
8339
  height: props.rowHeightInRem + "rem",
8240
8340
  pointerEvents: "none"
8241
8341
  }, children: internalRow.map((event) => (jsx(EventComp, { value: event, heightRem: props.rowHeightInRem, onEvent: onEvent }, event.id))) }, index));
8242
- }), !(timeLinePercentage < 0 || timeLinePercentage > 100) && (jsx(TimeLine, { color: "red", left: `${timeLinePercentage}%`, top: "0px", height: `100%` }))] }) })] }));
8342
+ }), indicatorRows.length > 0 && (jsx(IndicatorRow, { indicators: indicatorRows, selectedDate: selectedDate, visibleDays: visibleDays })), !(timeLinePercentage < 0 || timeLinePercentage > 100) && (jsx(TimeLine, { color: "red", left: `${timeLinePercentage}%`, top: "0px", height: `100%` }))] }) })] }));
8243
8343
  }, (prevProps, nextProps) => {
8244
8344
  // This memo is necessary to prevent re-render all the rows when a user makes drag and drop
8245
8345
  const getEventKey = (event) => {
@@ -8265,6 +8365,9 @@ const SchedulerRow = memo((props) => {
8265
8365
  return false;
8266
8366
  }
8267
8367
  }
8368
+ if (prevProps.indicatorRows.length !== nextProps.indicatorRows.length) {
8369
+ return false;
8370
+ }
8268
8371
  // If we got here, the events are the same
8269
8372
  return true;
8270
8373
  });
@@ -8455,51 +8558,6 @@ const getWeeksByDays = (visibleDays, selectedDate) => {
8455
8558
  return weeks;
8456
8559
  };
8457
8560
 
8458
- const eventIsVisible = (startDate, endDate, selectedDate, visibleDays) => {
8459
- const schedulerEnd = Temporal.PlainDate.from({
8460
- year: selectedDate.getFullYear(),
8461
- month: selectedDate.getMonth() + 1,
8462
- day: selectedDate.getDate(),
8463
- }).add({ days: visibleDays });
8464
- const schedulerEndDate = new Date(schedulerEnd.year, schedulerEnd.month - 1, schedulerEnd.day);
8465
- const isBefore = selectedDate > startDate && selectedDate > endDate;
8466
- const isAfter = schedulerEndDate < startDate && schedulerEndDate < endDate;
8467
- return !isBefore && !isAfter;
8468
- };
8469
-
8470
- const hoursBetween = (date1, date2) => {
8471
- const oneHourInMillis = 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
8472
- const timeDiff = date2.getTime() - date1.getTime();
8473
- return timeDiff / oneHourInMillis;
8474
- };
8475
- const getEventSizes = (schedulerDate, totalDays, startDate, endDate) => {
8476
- if (!eventIsVisible(startDate, endDate, schedulerDate, totalDays)) {
8477
- return {
8478
- left: 0,
8479
- width: 0,
8480
- };
8481
- }
8482
- const totalHours = totalDays * 24;
8483
- // const dateString = schedulerDate.toISOString().split('T')[0];
8484
- //const schedulerDateAtZero = new Date(dateString)
8485
- let startHours = hoursBetween(schedulerDate, startDate);
8486
- let durationHours = hoursBetween(startDate, endDate);
8487
- const startOutOfScheduler = startHours < 0;
8488
- if (startOutOfScheduler) {
8489
- durationHours += startHours;
8490
- startHours = 0;
8491
- }
8492
- const left = (startHours / totalHours) * 100;
8493
- // Minimum width equivalent to 15 minutes so zero/near-zero duration events stay visible
8494
- const MIN_WIDTH_HOURS = 0.25;
8495
- const minWidth = (MIN_WIDTH_HOURS * 100) / totalHours;
8496
- const width = Math.max((durationHours * 100) / totalHours, minWidth);
8497
- return {
8498
- left,
8499
- width,
8500
- };
8501
- };
8502
-
8503
8561
  function getLinesByDivisions(headerDivision, _) {
8504
8562
  const lines = headerDivision.flatMap((division) => division.bottom.map((_, i) => ({ isDayBoundary: i === 0 })));
8505
8563
  const left = 100 / lines.length;
@@ -8555,8 +8613,65 @@ const getNow = (isUtc) => {
8555
8613
  };
8556
8614
  const milliSecondsInSecond = 1000;
8557
8615
  const refreshMilliSeconds = 45 * milliSecondsInSecond;
8616
+ const EMPTY_EVENTS = [];
8617
+ const EMPTY_BG_EVENTS = [];
8618
+ const EMPTY_INDICATORS = [];
8619
+ // Memoised row body. Receives flat props (the per-row buckets are looked up
8620
+ // by the outer Row wrapper). As long as those bucket arrays keep their
8621
+ // reference (see `groupByRowIdStable`) and the rest of the render-context
8622
+ // values are stable, this row will skip re-rendering on selection clicks
8623
+ // that don't affect it.
8624
+ const RowContent = React__default.memo(function RowContent(props) {
8625
+ const header = {
8626
+ value: props.row,
8627
+ width: props.rowHeaderWidth,
8628
+ onEvent: props.onEvent,
8629
+ };
8630
+ return (jsx("div", { style: {
8631
+ ...props.style,
8632
+ backgroundColor: props.rowColor
8633
+ }, children: jsx(SchedulerRow, { events: props.events, backgroundEvents: props.backgroundEvents, indicatorRows: props.indicatorRows, rowHeader: header, EventComp: props.EventComp, BackgroundEventComp: props.BackgroundEventComp, RowTitleComp: props.RowTitleComp, weekendLines: props.weekendLines, divisionLines: props.divisionLines, timeLinePercentage: props.timeLinePercentage, rowHeightInRem: props.rowHeightRem, onEvent: props.onEvent, contextMenuItems: props.contextMenuItems, visibleDays: props.visibleDays, selectedDate: props.selectedDate }) }, props.row.rowId));
8634
+ });
8635
+ // Outer (unmemoised) row: looks up per-row buckets from the shared `data`
8636
+ // object and forwards them as flat props to `RowContent`. Because the
8637
+ // referenced arrays are reused when their contents are unchanged
8638
+ // (see `groupByRowIdStable`), `RowContent.memo` will skip the work.
8639
+ const Row = ({ index, style, data }) => {
8640
+ const row = data.rows[index];
8641
+ const rowColor = data.groupRowColors
8642
+ ? Math.floor(index / 2) % 2 === 0 ? data.evenColor : data.oddColor
8643
+ : index % 2 === 0 ? data.evenColor : data.oddColor;
8644
+ return (jsx(RowContent, { style: style, row: row, rowColor: rowColor, events: data.eventsByRow.get(row.rowId) ?? EMPTY_EVENTS, backgroundEvents: data.backgroundEventsByRow.get(row.rowId) ?? EMPTY_BG_EVENTS, indicatorRows: data.indicatorRowsByRow.get(row.rowId) ?? EMPTY_INDICATORS, EventComp: data.EventComp, BackgroundEventComp: data.BackgroundEventComp, RowTitleComp: data.RowTitleComp, rowHeaderWidth: data.rowHeaderWidth, rowHeightRem: data.rowHeightRem, weekendLines: data.weekendLines, divisionLines: data.divisionLines, timeLinePercentage: data.timeLinePercentage, selectedDate: data.selectedDate, visibleDays: data.visibleDays, contextMenuItems: data.contextMenuItems, onEvent: data.onEvent }));
8645
+ };
8646
+ // Bucket items by rowId, but reuse previous bucket arrays whenever a row's
8647
+ // contents have not changed. Keeping bucket references stable is essential
8648
+ // for `RowContent.memo` to skip work when only one row's events change
8649
+ // (e.g. on selection clicks).
8650
+ const groupByRowIdStable = (items, prev) => {
8651
+ const next = new Map();
8652
+ for (const item of items) {
8653
+ const bucket = next.get(item.rowId);
8654
+ if (bucket) {
8655
+ bucket.push(item);
8656
+ }
8657
+ else {
8658
+ next.set(item.rowId, [item]);
8659
+ }
8660
+ }
8661
+ if (!prev)
8662
+ return next;
8663
+ for (const [rowId, bucket] of next) {
8664
+ const prevBucket = prev.get(rowId);
8665
+ if (prevBucket
8666
+ && prevBucket.length === bucket.length
8667
+ && prevBucket.every((v, i) => v === bucket[i])) {
8668
+ next.set(rowId, prevBucket);
8669
+ }
8670
+ }
8671
+ return next;
8672
+ };
8558
8673
  const Scheduler = (props) => {
8559
- const { header: headerContent, id, events: eventsState, backgroundEvents, contextMenuItems, EventComp, RowTitleComp, orderCategories = ["title"], useOrderCategory, onEvent, groupRowColors, rowHeaderWidth = 180, rowHeightRem = 1.75, } = props;
8674
+ const { header: headerContent, id, events: eventsState, backgroundEvents, indicatorRows: allIndicatorRows = [], contextMenuItems, EventComp, RowTitleComp, orderCategories = ["title"], useOrderCategory, onEvent, groupRowColors, rowHeaderWidth = 180, rowHeightRem = 1.75, } = props;
8560
8675
  const BackgroundEventComp = props.BackgroundEventComp ?? BackgroundEvent;
8561
8676
  const rows = useOrderCategory === false
8562
8677
  ? props.rows
@@ -8564,21 +8679,49 @@ const Scheduler = (props) => {
8564
8679
  const instanceRef = useRef(null);
8565
8680
  // const rowHeaderWidth = "180px";
8566
8681
  const { selectedDate, visibleDays, isUtc, isHeaderVisible, visibleRows: stateVisibleRows } = props.state;
8567
- const events = filterVisibleEvents(eventsState, selectedDate, visibleDays);
8682
+ const events = useMemo(() => filterVisibleEvents(eventsState, selectedDate, visibleDays), [eventsState, selectedDate, visibleDays]);
8568
8683
  useEffect(() => {
8569
8684
  instanceRef.current?.resetAfterIndex(0);
8570
- }, [orderCategories, props.rows.length, events]);
8571
- const totalSubRows = rows.reduce((acc, row) => {
8572
- const filteredEvents = events.filter((it) => it.rowId === row.rowId);
8685
+ }, [orderCategories, props.rows.length, events, allIndicatorRows]);
8686
+ // Bucket events / backgroundEvents / indicatorRows by rowId once per render
8687
+ // instead of running events.filter() per row inside the row renderer.
8688
+ // Reuse previous bucket arrays when their contents are unchanged so the
8689
+ // memoised RowContent below can skip work (essential for selection clicks
8690
+ // to be cheap).
8691
+ const eventsByRowRef = useRef();
8692
+ const backgroundEventsByRowRef = useRef();
8693
+ const indicatorRowsByRowRef = useRef();
8694
+ const eventsByRow = useMemo(() => {
8695
+ const next = groupByRowIdStable(events, eventsByRowRef.current);
8696
+ eventsByRowRef.current = next;
8697
+ return next;
8698
+ }, [events]);
8699
+ const backgroundEventsByRow = useMemo(() => {
8700
+ const next = groupByRowIdStable(backgroundEvents, backgroundEventsByRowRef.current);
8701
+ backgroundEventsByRowRef.current = next;
8702
+ return next;
8703
+ }, [backgroundEvents]);
8704
+ const indicatorRowsByRow = useMemo(() => {
8705
+ const next = groupByRowIdStable(allIndicatorRows, indicatorRowsByRowRef.current);
8706
+ indicatorRowsByRowRef.current = next;
8707
+ return next;
8708
+ }, [allIndicatorRows]);
8709
+ const totalHeightRem = rows.reduce((acc, row) => {
8710
+ const filteredEvents = eventsByRow.get(row.rowId) ?? [];
8573
8711
  const innerRows = separateEventsToInnerRows(filteredEvents);
8574
- return acc + Math.max(innerRows.length, 1);
8712
+ const eventHeight = Math.max(innerRows.length, 1) * rowHeightRem;
8713
+ const hasIndicators = indicatorRowsByRow.has(row.rowId);
8714
+ return acc + eventHeight + (hasIndicators ? INDICATOR_HEIGHT_REM : 0);
8575
8715
  }, 0);
8576
- const visibleRows = Math.min(stateVisibleRows ?? totalSubRows, totalSubRows);
8716
+ const schedulerContentHeight = stateVisibleRows != null
8717
+ ? Math.min(totalHeightRem, stateVisibleRows * rowHeightRem)
8718
+ : Math.max(totalHeightRem, rowHeightRem);
8577
8719
  const totalHours = visibleDays * 24;
8578
8720
  const [timeLinePercentage, setTimeLinePercentage] = useState(0);
8721
+ const divisions = useMemo(() => getDefaultDivisions(visibleDays, selectedDate), [visibleDays, selectedDate]);
8579
8722
  const header = {
8580
8723
  content: headerContent,
8581
- divisions: getDefaultDivisions(visibleDays, selectedDate),
8724
+ divisions,
8582
8725
  visibleDays: visibleDays,
8583
8726
  width: rowHeaderWidth,
8584
8727
  selectedDate,
@@ -8588,9 +8731,8 @@ const Scheduler = (props) => {
8588
8731
  };
8589
8732
  const evenColor = "var(--cw-color-surface-container-low)";
8590
8733
  const oddColor = "var(--cw-color-surface-container)";
8591
- const schedulerContentHeight = rowHeightRem * Math.max(visibleRows, 1);
8592
- const weekendsLines = getWeekendLinesByDatesVisible(selectedDate, visibleDays);
8593
- const divisionLines = getLinesByDivisions(header.divisions);
8734
+ const weekendsLines = useMemo(() => getWeekendLinesByDatesVisible(selectedDate, visibleDays), [selectedDate, visibleDays]);
8735
+ const divisionLines = useMemo(() => getLinesByDivisions(divisions), [divisions, visibleDays]);
8594
8736
  // Timeline percentage calculation
8595
8737
  useEffect(() => {
8596
8738
  const updateTimeLinePercentage = () => {
@@ -8601,30 +8743,55 @@ const Scheduler = (props) => {
8601
8743
  const interval = setInterval(updateTimeLinePercentage, refreshMilliSeconds);
8602
8744
  return () => clearInterval(interval);
8603
8745
  }, [selectedDate, isUtc, totalHours]);
8604
- // Memoized Row Component
8605
- const Row = React__default.memo(({ index, style }) => {
8606
- const row = rows[index];
8607
- const rowColor = groupRowColors
8608
- ? Math.floor(index / 2) % 2 === 0 ? evenColor : oddColor
8609
- : index % 2 === 0 ? evenColor : oddColor;
8610
- const header = {
8611
- value: row,
8612
- width: rowHeaderWidth,
8613
- onEvent: onEvent,
8614
- };
8615
- return (jsx("div", { style: {
8616
- ...style,
8617
- backgroundColor: rowColor
8618
- }, children: jsx(SchedulerRow, { events: events.filter((it) => it.rowId === row.rowId), backgroundEvents: backgroundEvents.filter((it) => it.rowId === row.rowId), rowHeader: header, EventComp: EventComp, BackgroundEventComp: BackgroundEventComp, RowTitleComp: RowTitleComp, weekendLines: weekendsLines, divisionLines: divisionLines, timeLinePercentage: timeLinePercentage, rowHeightInRem: rowHeightRem, onEvent: onEvent, contextMenuItems: contextMenuItems, visibleDays: visibleDays, selectedDate: selectedDate }) }, row.rowId));
8619
- });
8746
+ const itemData = useMemo(() => ({
8747
+ rows,
8748
+ eventsByRow,
8749
+ backgroundEventsByRow,
8750
+ indicatorRowsByRow,
8751
+ EventComp: EventComp,
8752
+ BackgroundEventComp,
8753
+ RowTitleComp: RowTitleComp,
8754
+ rowHeaderWidth,
8755
+ rowHeightRem,
8756
+ weekendLines: weekendsLines,
8757
+ divisionLines,
8758
+ timeLinePercentage,
8759
+ selectedDate,
8760
+ visibleDays,
8761
+ contextMenuItems,
8762
+ onEvent,
8763
+ groupRowColors,
8764
+ evenColor,
8765
+ oddColor,
8766
+ }), [
8767
+ rows,
8768
+ eventsByRow,
8769
+ backgroundEventsByRow,
8770
+ indicatorRowsByRow,
8771
+ EventComp,
8772
+ BackgroundEventComp,
8773
+ RowTitleComp,
8774
+ rowHeaderWidth,
8775
+ rowHeightRem,
8776
+ weekendsLines,
8777
+ divisionLines,
8778
+ timeLinePercentage,
8779
+ selectedDate,
8780
+ visibleDays,
8781
+ contextMenuItems,
8782
+ onEvent,
8783
+ groupRowColors,
8784
+ ]);
8620
8785
  const getItemSize = useCallback((index) => {
8621
8786
  const row = rows[index];
8622
- const filteredEvents = events.filter((it) => it.rowId === row.rowId);
8787
+ const filteredEvents = eventsByRow.get(row.rowId) ?? [];
8623
8788
  const innerRows = separateEventsToInnerRows(filteredEvents);
8624
8789
  const rowsNumber = innerRows.length > 0 ? innerRows.length : 1;
8625
8790
  const pixelsInRem = 16;
8626
- return rowsNumber * rowHeightRem * pixelsInRem;
8627
- }, [rows, events]);
8791
+ const hasIndicators = indicatorRowsByRow.has(row.rowId);
8792
+ const indicatorPixels = hasIndicators ? INDICATOR_HEIGHT_REM * pixelsInRem : 0;
8793
+ return rowsNumber * rowHeightRem * pixelsInRem + indicatorPixels;
8794
+ }, [rows, eventsByRow, indicatorRowsByRow, rowHeightRem]);
8628
8795
  // Render
8629
8796
  return (jsxs("div", { id: id, style: {
8630
8797
  position: "relative",
@@ -8635,7 +8802,7 @@ const Scheduler = (props) => {
8635
8802
  position: "sticky",
8636
8803
  top: 0,
8637
8804
  zIndex: 1,
8638
- }, children: jsx(SchedulerHeader, { ...header }) })), jsx(VariableSizeList, { height: schedulerContentHeight * 16, itemCount: rows.length, itemSize: getItemSize, width: "100%", style: { overflowX: "hidden" }, ref: instanceRef, className: styles$2["hide-scrollbar"], children: Row })] }));
8805
+ }, children: jsx(SchedulerHeader, { ...header }) })), jsx(VariableSizeList, { height: schedulerContentHeight * 16, itemCount: rows.length, itemSize: getItemSize, itemData: itemData, width: "100%", style: { overflowX: "hidden" }, ref: instanceRef, className: styles$2["hide-scrollbar"], children: Row })] }));
8639
8806
  };
8640
8807
 
8641
8808
  class UiEvent {
@@ -8998,13 +9165,13 @@ const PinRowHeader = ({ value, width, onEvent }) => {
8998
9165
  jsxs("span", { className: styles["scheduler-crewmember-functions"], children: ["(", value.title3, ")"] }), value.subtitle2 && jsxs("span", { children: ["-", value.subtitle2] })] })] }), isLoading ? jsx("span", { className: "cwi-icons cwi-spinner" }) : undefined] }) }, value.rowId) }, value.rowId);
8999
9166
  };
9000
9167
 
9001
- const SuperScheduler = ({ id, state, header, rows, events, pinnedOrderCategory, unPinnedOrderCategory, backgroundEvents, contextMenuItems, onEvent, rowHeightRem = 1.75 }) => {
9168
+ const SuperScheduler = ({ id, state, header, rows, events, pinnedOrderCategory, unPinnedOrderCategory, backgroundEvents, indicatorRows = [], contextMenuItems, onEvent, rowHeightRem = 1.75 }) => {
9002
9169
  const pinnedRows = rows.filter((it) => it.isPinned);
9003
9170
  const notPinnedRows = rows.filter((it) => !it.isPinned);
9004
9171
  const isFirstVisible = pinnedRows.length > 0;
9005
- return (jsxs(Fragment, { children: [isFirstVisible && (jsxs(Fragment, { children: [jsx(Scheduler, { id: `${id}-pinned`, state: state, header: header, rows: pinnedRows, events: events, backgroundEvents: backgroundEvents, contextMenuItems: contextMenuItems, orderCategories: pinnedOrderCategory, onEvent: onEvent, EventComp: SchedulerEvent, RowTitleComp: PinRowHeader, rowHeightRem: rowHeightRem }), jsx("div", { children: jsx(CwButton, { onClick: () => {
9172
+ return (jsxs(Fragment, { children: [isFirstVisible && (jsxs(Fragment, { children: [jsx(Scheduler, { id: `${id}-pinned`, state: state, header: header, rows: pinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: pinnedOrderCategory, onEvent: onEvent, EventComp: SchedulerEvent, RowTitleComp: PinRowHeader, rowHeightRem: rowHeightRem }), jsx("div", { children: jsx(CwButton, { onClick: () => {
9006
9173
  onEvent(new OnClearPinned());
9007
- }, children: "Clear pinned" }) })] })), jsx(Scheduler, { id: `${id}-notPinned`, state: { ...state, isHeaderVisible: !isFirstVisible }, header: header, rows: notPinnedRows, events: events, backgroundEvents: backgroundEvents, contextMenuItems: contextMenuItems, orderCategories: unPinnedOrderCategory, onEvent: onEvent, EventComp: SchedulerEvent, RowTitleComp: PinRowHeader, rowHeightRem: rowHeightRem })] }));
9174
+ }, children: "Clear pinned" }) })] })), jsx(Scheduler, { id: `${id}-notPinned`, state: { ...state, isHeaderVisible: !isFirstVisible }, header: header, rows: notPinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: unPinnedOrderCategory, onEvent: onEvent, EventComp: SchedulerEvent, RowTitleComp: PinRowHeader, rowHeightRem: rowHeightRem })] }));
9008
9175
  };
9009
9176
 
9010
9177
  const CwFindAirport = ({ handleChange, searchType = "OnlyDatabase", placeHolder = "Search airport…", required = false, cblConfig, className = "", value, disabled = false, displayMode, initialDisplayText, labelProps, alignProps, width }) => {