react-schedule-picker 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/index.css +253 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +196 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +906 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DAY_LABELS: () => DAY_LABELS,
|
|
24
|
+
DAY_ORDER: () => DAY_ORDER,
|
|
25
|
+
DEFAULT_PRESETS: () => DEFAULT_PRESETS,
|
|
26
|
+
HOURS: () => HOURS,
|
|
27
|
+
LOCALE_PRESETS: () => LOCALE_PRESETS,
|
|
28
|
+
SchedulePicker: () => SchedulePicker,
|
|
29
|
+
applyRect: () => applyRect,
|
|
30
|
+
countSelectedHours: () => countSelectedHours,
|
|
31
|
+
createEmptySchedule: () => createEmptySchedule,
|
|
32
|
+
createFullSchedule: () => createFullSchedule,
|
|
33
|
+
formatHourHeader: () => formatHourHeader,
|
|
34
|
+
fromRanges: () => fromRanges,
|
|
35
|
+
generateSlots: () => generateSlots,
|
|
36
|
+
getDefaultPresets: () => getDefaultPresets,
|
|
37
|
+
hasHour: () => hasHour,
|
|
38
|
+
isAllSelected: () => isAllSelected,
|
|
39
|
+
isSlotDisabled: () => isSlotDisabled,
|
|
40
|
+
resolveLocaleConfig: () => resolveLocaleConfig,
|
|
41
|
+
rotateDays: () => rotateDays,
|
|
42
|
+
slotToTimeString: () => slotToTimeString,
|
|
43
|
+
summarizeSchedule: () => summarizeSchedule,
|
|
44
|
+
toISO: () => toISO,
|
|
45
|
+
toRanges: () => toRanges,
|
|
46
|
+
toggleHour: () => toggleHour
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/SchedulePicker.tsx
|
|
51
|
+
var import_react = require("react");
|
|
52
|
+
|
|
53
|
+
// src/constants.ts
|
|
54
|
+
var DAY_ORDER = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
55
|
+
var DAY_LABELS = {
|
|
56
|
+
mon: "Mon",
|
|
57
|
+
tue: "Tue",
|
|
58
|
+
wed: "Wed",
|
|
59
|
+
thu: "Thu",
|
|
60
|
+
fri: "Fri",
|
|
61
|
+
sat: "Sat",
|
|
62
|
+
sun: "Sun"
|
|
63
|
+
};
|
|
64
|
+
var HOURS = Array.from({ length: 24 }, (_, i) => i);
|
|
65
|
+
var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri"];
|
|
66
|
+
var WEEKENDS = ["sat", "sun"];
|
|
67
|
+
var DAY_HOURS = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
|
|
68
|
+
var NIGHT_HOURS = [18, 19, 20, 21, 22, 23, 0, 1, 2, 3, 4, 5];
|
|
69
|
+
var DEFAULT_PRESETS = [
|
|
70
|
+
{ label: "Weekday Day", days: WEEKDAYS, hours: DAY_HOURS },
|
|
71
|
+
{ label: "Weekday Night", days: WEEKDAYS, hours: NIGHT_HOURS },
|
|
72
|
+
{ label: "Weekend Day", days: WEEKENDS, hours: DAY_HOURS },
|
|
73
|
+
{ label: "Weekend Night", days: WEEKENDS, hours: NIGHT_HOURS }
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// src/utils.ts
|
|
77
|
+
function generateSlots(minHour, maxHour, hourlyChunks) {
|
|
78
|
+
const slots = [];
|
|
79
|
+
const step = 1 / hourlyChunks;
|
|
80
|
+
for (let h = minHour; h <= maxHour; h++) {
|
|
81
|
+
for (let c = 0; c < hourlyChunks; c++) {
|
|
82
|
+
slots.push(h + c * step);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return slots;
|
|
86
|
+
}
|
|
87
|
+
function slotToTimeString(slot) {
|
|
88
|
+
const hour = Math.floor(slot);
|
|
89
|
+
const minutes = Math.round((slot - hour) * 60);
|
|
90
|
+
return `${hour}:${String(minutes).padStart(2, "0")}`;
|
|
91
|
+
}
|
|
92
|
+
function hasHour(schedule, day, hour) {
|
|
93
|
+
return schedule[day]?.includes(hour) ?? false;
|
|
94
|
+
}
|
|
95
|
+
function isSlotDisabled(disabledSlots, day, hour) {
|
|
96
|
+
if (!disabledSlots) return false;
|
|
97
|
+
return disabledSlots[day]?.includes(hour) ?? false;
|
|
98
|
+
}
|
|
99
|
+
function toggleHour(schedule, day, hour, selected) {
|
|
100
|
+
const current = schedule[day] ?? [];
|
|
101
|
+
const next = selected ? [.../* @__PURE__ */ new Set([...current, hour])].sort((a, b) => a - b) : current.filter((h) => h !== hour);
|
|
102
|
+
return { ...schedule, [day]: next };
|
|
103
|
+
}
|
|
104
|
+
function toggleHourWithMax(schedule, day, hour, selected, maxSelections, disabledSlots) {
|
|
105
|
+
if (isSlotDisabled(disabledSlots, day, hour)) return schedule;
|
|
106
|
+
if (selected && maxSelections !== void 0) {
|
|
107
|
+
const currentCount = countSelectedHours(schedule);
|
|
108
|
+
if (currentCount >= maxSelections) return schedule;
|
|
109
|
+
}
|
|
110
|
+
return toggleHour(schedule, day, hour, selected);
|
|
111
|
+
}
|
|
112
|
+
function applyRect(base, visibleDays, startDayIdx, startHour, endDayIdx, endHour, selected, slots, maxSelections, disabledSlots) {
|
|
113
|
+
const minDayIdx = Math.min(startDayIdx, endDayIdx);
|
|
114
|
+
const maxDayIdx = Math.max(startDayIdx, endDayIdx);
|
|
115
|
+
const startSlotIdx = slots.indexOf(startHour);
|
|
116
|
+
const endSlotIdx = slots.indexOf(endHour);
|
|
117
|
+
const minSlotIdx = Math.min(startSlotIdx, endSlotIdx);
|
|
118
|
+
const maxSlotIdx = Math.max(startSlotIdx, endSlotIdx);
|
|
119
|
+
let next = { ...base };
|
|
120
|
+
for (let di = minDayIdx; di <= maxDayIdx; di++) {
|
|
121
|
+
const day = visibleDays[di];
|
|
122
|
+
for (let si = minSlotIdx; si <= maxSlotIdx; si++) {
|
|
123
|
+
next = toggleHourWithMax(next, day, slots[si], selected, maxSelections, disabledSlots);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return next;
|
|
127
|
+
}
|
|
128
|
+
function formatHourHeader(hour) {
|
|
129
|
+
if (Number.isInteger(hour) && hour % 3 === 0) return String(hour);
|
|
130
|
+
return "\xB7";
|
|
131
|
+
}
|
|
132
|
+
function countSelectedHours(schedule) {
|
|
133
|
+
return Object.values(schedule).reduce(
|
|
134
|
+
(sum, hours) => sum + (hours?.length ?? 0),
|
|
135
|
+
0
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function isAllSelected(schedule, visibleDays = DAY_ORDER) {
|
|
139
|
+
return visibleDays.every((day) => (schedule[day]?.length ?? 0) === 24);
|
|
140
|
+
}
|
|
141
|
+
function summarizeSchedule(schedule, dayLabels = {
|
|
142
|
+
mon: "Mon",
|
|
143
|
+
tue: "Tue",
|
|
144
|
+
wed: "Wed",
|
|
145
|
+
thu: "Thu",
|
|
146
|
+
fri: "Fri",
|
|
147
|
+
sat: "Sat",
|
|
148
|
+
sun: "Sun"
|
|
149
|
+
}, visibleDays = DAY_ORDER, hourlyChunks = 1, noSelectionLabel = "No selection") {
|
|
150
|
+
const step = 1 / hourlyChunks;
|
|
151
|
+
const entries = visibleDays.filter((day) => (schedule[day]?.length ?? 0) > 0).map((day) => {
|
|
152
|
+
const slots = [...schedule[day] ?? []].sort((a, b) => a - b);
|
|
153
|
+
const ranges = [];
|
|
154
|
+
let start = slots[0];
|
|
155
|
+
let prev = slots[0];
|
|
156
|
+
for (let i = 1; i <= slots.length; i++) {
|
|
157
|
+
if (i < slots.length && Math.abs(slots[i] - prev - step) < 1e-3) {
|
|
158
|
+
prev = slots[i];
|
|
159
|
+
} else {
|
|
160
|
+
const endTime = prev + step;
|
|
161
|
+
ranges.push(`${slotToTimeString(start)}-${slotToTimeString(endTime)}`);
|
|
162
|
+
start = slots[i];
|
|
163
|
+
prev = slots[i];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return `${dayLabels[day] ?? day}: ${ranges.join(", ")}`;
|
|
167
|
+
});
|
|
168
|
+
return entries.length > 0 ? entries.join(" | ") : noSelectionLabel;
|
|
169
|
+
}
|
|
170
|
+
function createEmptySchedule() {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
function createFullSchedule(visibleDays = DAY_ORDER) {
|
|
174
|
+
const schedule = {};
|
|
175
|
+
for (const day of visibleDays) {
|
|
176
|
+
schedule[day] = [...HOURS];
|
|
177
|
+
}
|
|
178
|
+
return schedule;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/locales.ts
|
|
182
|
+
var format24h = (h) => String(h);
|
|
183
|
+
var format12hAmPm = (h) => {
|
|
184
|
+
if (h === 0) return "12AM";
|
|
185
|
+
if (h === 12) return "12PM";
|
|
186
|
+
if (h < 12) return String(h);
|
|
187
|
+
return String(h - 12);
|
|
188
|
+
};
|
|
189
|
+
var PRESET_LABELS = {
|
|
190
|
+
en: {
|
|
191
|
+
weekdayDay: "Weekday Day",
|
|
192
|
+
weekdayNight: "Weekday Night",
|
|
193
|
+
weekendDay: "Weekend Day",
|
|
194
|
+
weekendNight: "Weekend Night"
|
|
195
|
+
},
|
|
196
|
+
ko: {
|
|
197
|
+
weekdayDay: "\uD3C9\uC77C \uC8FC\uAC04",
|
|
198
|
+
weekdayNight: "\uD3C9\uC77C \uC57C\uAC04",
|
|
199
|
+
weekendDay: "\uC8FC\uB9D0 \uC8FC\uAC04",
|
|
200
|
+
weekendNight: "\uC8FC\uB9D0 \uC57C\uAC04"
|
|
201
|
+
},
|
|
202
|
+
ja: {
|
|
203
|
+
weekdayDay: "\u5E73\u65E5 \u65E5\u4E2D",
|
|
204
|
+
weekdayNight: "\u5E73\u65E5 \u591C\u9593",
|
|
205
|
+
weekendDay: "\u9031\u672B \u65E5\u4E2D",
|
|
206
|
+
weekendNight: "\u9031\u672B \u591C\u9593"
|
|
207
|
+
},
|
|
208
|
+
zhCN: {
|
|
209
|
+
weekdayDay: "\u5DE5\u4F5C\u65E5\u767D\u5929",
|
|
210
|
+
weekdayNight: "\u5DE5\u4F5C\u65E5\u591C\u95F4",
|
|
211
|
+
weekendDay: "\u5468\u672B\u767D\u5929",
|
|
212
|
+
weekendNight: "\u5468\u672B\u591C\u95F4"
|
|
213
|
+
},
|
|
214
|
+
zhTW: {
|
|
215
|
+
weekdayDay: "\u5DE5\u4F5C\u65E5\u767D\u5929",
|
|
216
|
+
weekdayNight: "\u5DE5\u4F5C\u65E5\u591C\u9593",
|
|
217
|
+
weekendDay: "\u9031\u672B\u767D\u5929",
|
|
218
|
+
weekendNight: "\u9031\u672B\u591C\u9593"
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var DAY_LABELS_BY_LOCALE = {
|
|
222
|
+
en: { mon: "Mon", tue: "Tue", wed: "Wed", thu: "Thu", fri: "Fri", sat: "Sat", sun: "Sun" },
|
|
223
|
+
ko: { mon: "\uC6D4", tue: "\uD654", wed: "\uC218", thu: "\uBAA9", fri: "\uAE08", sat: "\uD1A0", sun: "\uC77C" },
|
|
224
|
+
ja: { mon: "\u6708", tue: "\u706B", wed: "\u6C34", thu: "\u6728", fri: "\u91D1", sat: "\u571F", sun: "\u65E5" },
|
|
225
|
+
zhCN: { mon: "\u5468\u4E00", tue: "\u5468\u4E8C", wed: "\u5468\u4E09", thu: "\u5468\u56DB", fri: "\u5468\u4E94", sat: "\u5468\u516D", sun: "\u5468\u65E5" },
|
|
226
|
+
zhTW: { mon: "\u9031\u4E00", tue: "\u9031\u4E8C", wed: "\u9031\u4E09", thu: "\u9031\u56DB", fri: "\u9031\u4E94", sat: "\u9031\u516D", sun: "\u9031\u65E5" }
|
|
227
|
+
};
|
|
228
|
+
var LOCALE_PRESETS = {
|
|
229
|
+
"en": {
|
|
230
|
+
messages: {
|
|
231
|
+
clear: "Clear",
|
|
232
|
+
noSelection: "No selection",
|
|
233
|
+
presets: PRESET_LABELS.en
|
|
234
|
+
},
|
|
235
|
+
weekStartsOn: "mon",
|
|
236
|
+
weekendHighlight: "none",
|
|
237
|
+
formatHour: format24h,
|
|
238
|
+
dayLabels: DAY_LABELS_BY_LOCALE.en
|
|
239
|
+
},
|
|
240
|
+
"en-US": {
|
|
241
|
+
messages: {
|
|
242
|
+
clear: "Clear",
|
|
243
|
+
noSelection: "No selection",
|
|
244
|
+
presets: PRESET_LABELS.en
|
|
245
|
+
},
|
|
246
|
+
weekStartsOn: "sun",
|
|
247
|
+
weekendHighlight: "none",
|
|
248
|
+
formatHour: format12hAmPm,
|
|
249
|
+
dayLabels: DAY_LABELS_BY_LOCALE.en
|
|
250
|
+
},
|
|
251
|
+
"ko": {
|
|
252
|
+
messages: {
|
|
253
|
+
clear: "\uCD08\uAE30\uD654",
|
|
254
|
+
noSelection: "\uC120\uD0DD \uC5C6\uC74C",
|
|
255
|
+
presets: PRESET_LABELS.ko
|
|
256
|
+
},
|
|
257
|
+
weekStartsOn: "mon",
|
|
258
|
+
weekendHighlight: { sat: "#2d6af6", sun: "#f04646" },
|
|
259
|
+
formatHour: format24h,
|
|
260
|
+
dayLabels: DAY_LABELS_BY_LOCALE.ko
|
|
261
|
+
},
|
|
262
|
+
"ja": {
|
|
263
|
+
messages: {
|
|
264
|
+
clear: "\u30AF\u30EA\u30A2",
|
|
265
|
+
noSelection: "\u672A\u9078\u629E",
|
|
266
|
+
presets: PRESET_LABELS.ja
|
|
267
|
+
},
|
|
268
|
+
weekStartsOn: "sun",
|
|
269
|
+
weekendHighlight: { sat: "#2d6af6", sun: "#f04646" },
|
|
270
|
+
formatHour: format24h,
|
|
271
|
+
dayLabels: DAY_LABELS_BY_LOCALE.ja
|
|
272
|
+
},
|
|
273
|
+
"zh-CN": {
|
|
274
|
+
messages: {
|
|
275
|
+
clear: "\u6E05\u9664",
|
|
276
|
+
noSelection: "\u672A\u9009\u62E9",
|
|
277
|
+
presets: PRESET_LABELS.zhCN
|
|
278
|
+
},
|
|
279
|
+
weekStartsOn: "mon",
|
|
280
|
+
weekendHighlight: { sat: "#f04646", sun: "#f04646" },
|
|
281
|
+
formatHour: format24h,
|
|
282
|
+
dayLabels: DAY_LABELS_BY_LOCALE.zhCN
|
|
283
|
+
},
|
|
284
|
+
"zh-TW": {
|
|
285
|
+
messages: {
|
|
286
|
+
clear: "\u6E05\u9664",
|
|
287
|
+
noSelection: "\u672A\u9078\u64C7",
|
|
288
|
+
presets: PRESET_LABELS.zhTW
|
|
289
|
+
},
|
|
290
|
+
weekStartsOn: "mon",
|
|
291
|
+
weekendHighlight: { sat: "#f04646", sun: "#f04646" },
|
|
292
|
+
formatHour: format24h,
|
|
293
|
+
dayLabels: DAY_LABELS_BY_LOCALE.zhTW
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function resolveLocaleConfig(input = {}) {
|
|
297
|
+
const preset = LOCALE_PRESETS[input.locale ?? "en"] ?? LOCALE_PRESETS.en;
|
|
298
|
+
const mergedMessages = {
|
|
299
|
+
clear: input.messages?.clear ?? preset.messages.clear,
|
|
300
|
+
noSelection: input.messages?.noSelection ?? preset.messages.noSelection,
|
|
301
|
+
presets: {
|
|
302
|
+
...preset.messages.presets,
|
|
303
|
+
...input.messages?.presets
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const rawHighlight = input.weekendHighlight ?? preset.weekendHighlight;
|
|
307
|
+
const normalizedHighlight = rawHighlight === "none" ? {} : rawHighlight;
|
|
308
|
+
return {
|
|
309
|
+
messages: mergedMessages,
|
|
310
|
+
weekStartsOn: input.weekStartsOn ?? preset.weekStartsOn,
|
|
311
|
+
weekendHighlight: normalizedHighlight,
|
|
312
|
+
formatHour: preset.formatHour,
|
|
313
|
+
dayLabels: preset.dayLabels
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function rotateDays(weekStartsOn) {
|
|
317
|
+
const base = [...DAY_ORDER];
|
|
318
|
+
const startIdx = base.indexOf(weekStartsOn);
|
|
319
|
+
if (startIdx <= 0) return base;
|
|
320
|
+
return [...base.slice(startIdx), ...base.slice(0, startIdx)];
|
|
321
|
+
}
|
|
322
|
+
var WEEKDAYS2 = ["mon", "tue", "wed", "thu", "fri"];
|
|
323
|
+
var WEEKENDS2 = ["sat", "sun"];
|
|
324
|
+
var DAY_HOURS2 = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
|
|
325
|
+
var NIGHT_HOURS2 = [18, 19, 20, 21, 22, 23, 0, 1, 2, 3, 4, 5];
|
|
326
|
+
function getDefaultPresets(messages) {
|
|
327
|
+
return [
|
|
328
|
+
{ label: messages.presets.weekdayDay, days: WEEKDAYS2, hours: DAY_HOURS2 },
|
|
329
|
+
{ label: messages.presets.weekdayNight, days: WEEKDAYS2, hours: NIGHT_HOURS2 },
|
|
330
|
+
{ label: messages.presets.weekendDay, days: WEEKENDS2, hours: DAY_HOURS2 },
|
|
331
|
+
{ label: messages.presets.weekendNight, days: WEEKENDS2, hours: NIGHT_HOURS2 }
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/SchedulePicker.tsx
|
|
336
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
337
|
+
function SchedulePicker({
|
|
338
|
+
value,
|
|
339
|
+
onChange,
|
|
340
|
+
onSelectEnd,
|
|
341
|
+
presets: presetsProp,
|
|
342
|
+
hideToolbar = false,
|
|
343
|
+
readOnly = false,
|
|
344
|
+
disabled = false,
|
|
345
|
+
minHour = 0,
|
|
346
|
+
maxHour = 23,
|
|
347
|
+
visibleDays: visibleDaysProp,
|
|
348
|
+
dayLabels: dayLabelsProp,
|
|
349
|
+
dayAxis = "x",
|
|
350
|
+
compactHourLabels = false,
|
|
351
|
+
formatHour,
|
|
352
|
+
disabledSlots,
|
|
353
|
+
className,
|
|
354
|
+
// Localization props
|
|
355
|
+
locale,
|
|
356
|
+
messages,
|
|
357
|
+
weekStartsOn,
|
|
358
|
+
weekendHighlight
|
|
359
|
+
}) {
|
|
360
|
+
const resolvedLocale = (0, import_react.useMemo)(
|
|
361
|
+
() => resolveLocaleConfig({ locale, messages, weekStartsOn, weekendHighlight }),
|
|
362
|
+
[locale, messages, weekStartsOn, weekendHighlight]
|
|
363
|
+
);
|
|
364
|
+
const dayLabels = dayLabelsProp ?? resolvedLocale.dayLabels;
|
|
365
|
+
const effectiveFormatHour = formatHour ?? resolvedLocale.formatHour;
|
|
366
|
+
const visibleDays = (0, import_react.useMemo)(
|
|
367
|
+
() => visibleDaysProp ?? rotateDays(resolvedLocale.weekStartsOn),
|
|
368
|
+
[visibleDaysProp, resolvedLocale.weekStartsOn]
|
|
369
|
+
);
|
|
370
|
+
const activePresets = (0, import_react.useMemo)(
|
|
371
|
+
() => presetsProp ?? getDefaultPresets(resolvedLocale.messages),
|
|
372
|
+
[presetsProp, resolvedLocale.messages]
|
|
373
|
+
);
|
|
374
|
+
const [isDragging, setIsDragging] = (0, import_react.useState)(false);
|
|
375
|
+
const [isHeaderDragging, setIsHeaderDragging] = (0, import_react.useState)(false);
|
|
376
|
+
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
|
|
377
|
+
const [hoveredHour, setHoveredHour] = (0, import_react.useState)(null);
|
|
378
|
+
const [cornerHovered, setCornerHovered] = (0, import_react.useState)(false);
|
|
379
|
+
const dragSelectRef = (0, import_react.useRef)(true);
|
|
380
|
+
const headerDragSelectRef = (0, import_react.useRef)(true);
|
|
381
|
+
const headerStartRef = (0, import_react.useRef)(0);
|
|
382
|
+
const baseRef = (0, import_react.useRef)(value);
|
|
383
|
+
const startRef = (0, import_react.useRef)({ dayIdx: 0, slotIdx: 0 });
|
|
384
|
+
const headerBaseRef = (0, import_react.useRef)(value);
|
|
385
|
+
const tableRef = (0, import_react.useRef)(null);
|
|
386
|
+
const slots = (0, import_react.useMemo)(
|
|
387
|
+
() => generateSlots(minHour, maxHour, 1),
|
|
388
|
+
[minHour, maxHour]
|
|
389
|
+
);
|
|
390
|
+
const getHourLabel = (0, import_react.useCallback)(
|
|
391
|
+
(slot) => {
|
|
392
|
+
if (formatHour) return formatHour(slot);
|
|
393
|
+
if (compactHourLabels) return formatHourHeader(slot);
|
|
394
|
+
return effectiveFormatHour(slot);
|
|
395
|
+
},
|
|
396
|
+
[formatHour, effectiveFormatHour, compactHourLabels]
|
|
397
|
+
);
|
|
398
|
+
const handleCellPointerDown = (0, import_react.useCallback)(
|
|
399
|
+
(day, slot) => {
|
|
400
|
+
if (readOnly || isSlotDisabled(disabledSlots, day, slot)) return;
|
|
401
|
+
const dayIdx = visibleDays.indexOf(day);
|
|
402
|
+
const slotIdx = slots.indexOf(slot);
|
|
403
|
+
const willSelect = !hasHour(value, day, slot);
|
|
404
|
+
dragSelectRef.current = willSelect;
|
|
405
|
+
baseRef.current = value;
|
|
406
|
+
startRef.current = { dayIdx, slotIdx };
|
|
407
|
+
setIsDragging(true);
|
|
408
|
+
navigator.vibrate?.(8);
|
|
409
|
+
onChange(toggleHourWithMax(value, day, slot, willSelect, void 0, disabledSlots));
|
|
410
|
+
},
|
|
411
|
+
[value, onChange, visibleDays, slots, readOnly, disabledSlots]
|
|
412
|
+
);
|
|
413
|
+
const handleCellPointerOver = (0, import_react.useCallback)(
|
|
414
|
+
(day, slot) => {
|
|
415
|
+
if (!isDragging || readOnly) return;
|
|
416
|
+
const dayIdx = visibleDays.indexOf(day);
|
|
417
|
+
const slotIdx = slots.indexOf(slot);
|
|
418
|
+
const { dayIdx: sd, slotIdx: ss } = startRef.current;
|
|
419
|
+
const next = applyRect(baseRef.current, visibleDays, sd, slots[ss], dayIdx, slot, dragSelectRef.current, slots, void 0, disabledSlots);
|
|
420
|
+
onChange(next);
|
|
421
|
+
},
|
|
422
|
+
[isDragging, onChange, visibleDays, slots, readOnly, disabledSlots]
|
|
423
|
+
);
|
|
424
|
+
const handlePointerUp = (0, import_react.useCallback)(() => {
|
|
425
|
+
if (isDragging || isHeaderDragging) {
|
|
426
|
+
onSelectEnd?.(value);
|
|
427
|
+
}
|
|
428
|
+
setIsDragging(false);
|
|
429
|
+
setIsHeaderDragging(false);
|
|
430
|
+
}, [isDragging, isHeaderDragging, onSelectEnd, value]);
|
|
431
|
+
const applyDayRange = (0, import_react.useCallback)(
|
|
432
|
+
(base, startIdx, endIdx, selected) => {
|
|
433
|
+
const minIdx = Math.min(startIdx, endIdx);
|
|
434
|
+
const maxIdx = Math.max(startIdx, endIdx);
|
|
435
|
+
let next = { ...base };
|
|
436
|
+
for (let i = minIdx; i <= maxIdx; i++) {
|
|
437
|
+
const day = visibleDays[i];
|
|
438
|
+
if (selected) {
|
|
439
|
+
const enabledSlots = slots.filter((s) => !isSlotDisabled(disabledSlots, day, s));
|
|
440
|
+
next = { ...next, [day]: enabledSlots };
|
|
441
|
+
} else {
|
|
442
|
+
next = { ...next, [day]: [] };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return next;
|
|
446
|
+
},
|
|
447
|
+
[visibleDays, slots, disabledSlots]
|
|
448
|
+
);
|
|
449
|
+
const applyHourRange = (0, import_react.useCallback)(
|
|
450
|
+
(base, startIdx, endIdx, selected) => {
|
|
451
|
+
const minIdx = Math.min(startIdx, endIdx);
|
|
452
|
+
const maxIdx = Math.max(startIdx, endIdx);
|
|
453
|
+
let next = { ...base };
|
|
454
|
+
for (let i = minIdx; i <= maxIdx; i++) {
|
|
455
|
+
const slot = slots[i];
|
|
456
|
+
for (const d of visibleDays) {
|
|
457
|
+
next = toggleHourWithMax(next, d, slot, selected, void 0, disabledSlots);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return next;
|
|
461
|
+
},
|
|
462
|
+
[visibleDays, slots, disabledSlots]
|
|
463
|
+
);
|
|
464
|
+
const handleDayHeaderDown = (0, import_react.useCallback)(
|
|
465
|
+
(day) => {
|
|
466
|
+
if (readOnly) return;
|
|
467
|
+
const dayIdx = visibleDays.indexOf(day);
|
|
468
|
+
const current = value[day] ?? [];
|
|
469
|
+
const enabledSlots = slots.filter((s) => !isSlotDisabled(disabledSlots, day, s));
|
|
470
|
+
const allSelected = enabledSlots.length > 0 && enabledSlots.every((s) => current.includes(s));
|
|
471
|
+
const willSelect = !allSelected;
|
|
472
|
+
headerDragSelectRef.current = willSelect;
|
|
473
|
+
headerStartRef.current = dayIdx;
|
|
474
|
+
headerBaseRef.current = value;
|
|
475
|
+
setIsHeaderDragging("day");
|
|
476
|
+
navigator.vibrate?.(8);
|
|
477
|
+
onChange(applyDayRange(value, dayIdx, dayIdx, willSelect));
|
|
478
|
+
},
|
|
479
|
+
[value, onChange, slots, visibleDays, readOnly, disabledSlots, applyDayRange]
|
|
480
|
+
);
|
|
481
|
+
const handleDayHeaderOver = (0, import_react.useCallback)(
|
|
482
|
+
(day) => {
|
|
483
|
+
if (isHeaderDragging !== "day" || readOnly) return;
|
|
484
|
+
const dayIdx = visibleDays.indexOf(day);
|
|
485
|
+
onChange(applyDayRange(headerBaseRef.current, headerStartRef.current, dayIdx, headerDragSelectRef.current));
|
|
486
|
+
},
|
|
487
|
+
[isHeaderDragging, readOnly, visibleDays, onChange, applyDayRange]
|
|
488
|
+
);
|
|
489
|
+
const handleHourHeaderDown = (0, import_react.useCallback)(
|
|
490
|
+
(slot) => {
|
|
491
|
+
if (readOnly) return;
|
|
492
|
+
const slotIdx = slots.indexOf(slot);
|
|
493
|
+
const allSelected = visibleDays.every((d) => isSlotDisabled(disabledSlots, d, slot) || hasHour(value, d, slot));
|
|
494
|
+
const willSelect = !allSelected;
|
|
495
|
+
headerDragSelectRef.current = willSelect;
|
|
496
|
+
headerStartRef.current = slotIdx;
|
|
497
|
+
headerBaseRef.current = value;
|
|
498
|
+
setIsHeaderDragging("hour");
|
|
499
|
+
navigator.vibrate?.(8);
|
|
500
|
+
onChange(applyHourRange(value, slotIdx, slotIdx, willSelect));
|
|
501
|
+
},
|
|
502
|
+
[value, onChange, slots, visibleDays, readOnly, disabledSlots, applyHourRange]
|
|
503
|
+
);
|
|
504
|
+
const handleHourHeaderOver = (0, import_react.useCallback)(
|
|
505
|
+
(slot) => {
|
|
506
|
+
if (isHeaderDragging !== "hour" || readOnly) return;
|
|
507
|
+
const slotIdx = slots.indexOf(slot);
|
|
508
|
+
onChange(applyHourRange(headerBaseRef.current, headerStartRef.current, slotIdx, headerDragSelectRef.current));
|
|
509
|
+
},
|
|
510
|
+
[isHeaderDragging, readOnly, slots, onChange, applyHourRange]
|
|
511
|
+
);
|
|
512
|
+
const handleTouchMove = (0, import_react.useCallback)(
|
|
513
|
+
(e) => {
|
|
514
|
+
if (!isDragging && !isHeaderDragging || readOnly) return;
|
|
515
|
+
e.preventDefault();
|
|
516
|
+
const touch = e.touches[0];
|
|
517
|
+
const element = document.elementFromPoint(
|
|
518
|
+
touch.clientX,
|
|
519
|
+
touch.clientY
|
|
520
|
+
);
|
|
521
|
+
if (!element) return;
|
|
522
|
+
if (isDragging) {
|
|
523
|
+
const day = element.dataset.day;
|
|
524
|
+
const hourStr = element.dataset.hour;
|
|
525
|
+
if (day && hourStr) {
|
|
526
|
+
handleCellPointerOver(day, Number(hourStr));
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (isHeaderDragging === "day") {
|
|
531
|
+
const dayHeader = element.dataset.dayHeader;
|
|
532
|
+
if (dayHeader) {
|
|
533
|
+
handleDayHeaderOver(dayHeader);
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (isHeaderDragging === "hour") {
|
|
538
|
+
const hourHeader = element.dataset.hourHeader;
|
|
539
|
+
if (hourHeader) {
|
|
540
|
+
handleHourHeaderOver(Number(hourHeader));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
[
|
|
545
|
+
isDragging,
|
|
546
|
+
isHeaderDragging,
|
|
547
|
+
readOnly,
|
|
548
|
+
handleCellPointerOver,
|
|
549
|
+
handleDayHeaderOver,
|
|
550
|
+
handleHourHeaderOver
|
|
551
|
+
]
|
|
552
|
+
);
|
|
553
|
+
const handleToggleAll = (0, import_react.useCallback)(() => {
|
|
554
|
+
if (readOnly) return;
|
|
555
|
+
const allSelected = visibleDays.every((day) => {
|
|
556
|
+
const enabledSlots = slots.filter((s) => !isSlotDisabled(disabledSlots, day, s));
|
|
557
|
+
const current = value[day] ?? [];
|
|
558
|
+
return enabledSlots.length > 0 && enabledSlots.every((s) => current.includes(s));
|
|
559
|
+
});
|
|
560
|
+
if (allSelected) {
|
|
561
|
+
onChange({});
|
|
562
|
+
} else {
|
|
563
|
+
const next = {};
|
|
564
|
+
for (const day of visibleDays) {
|
|
565
|
+
next[day] = slots.filter((s) => !isSlotDisabled(disabledSlots, day, s));
|
|
566
|
+
}
|
|
567
|
+
onChange(next);
|
|
568
|
+
}
|
|
569
|
+
}, [value, onChange, visibleDays, slots, readOnly, disabledSlots]);
|
|
570
|
+
const handlePreset = (0, import_react.useCallback)(
|
|
571
|
+
(preset) => {
|
|
572
|
+
if (readOnly) return;
|
|
573
|
+
const next = {};
|
|
574
|
+
for (const d of preset.days) {
|
|
575
|
+
next[d] = [...preset.hours].filter((h) => !isSlotDisabled(disabledSlots, d, h)).sort((a, b) => a - b);
|
|
576
|
+
}
|
|
577
|
+
onChange(next);
|
|
578
|
+
},
|
|
579
|
+
[onChange, readOnly, disabledSlots]
|
|
580
|
+
);
|
|
581
|
+
const getDayLabelClass = (day) => {
|
|
582
|
+
const classes = ["rsp-day-label"];
|
|
583
|
+
if (resolvedLocale.weekendHighlight[day]) {
|
|
584
|
+
classes.push(`rsp-day-label--${day}`);
|
|
585
|
+
}
|
|
586
|
+
if (readOnly) classes.push("rsp-day-label--readonly");
|
|
587
|
+
return classes.join(" ");
|
|
588
|
+
};
|
|
589
|
+
const getDayLabelStyle = (day) => {
|
|
590
|
+
const color = resolvedLocale.weekendHighlight[day];
|
|
591
|
+
return color ? { color } : void 0;
|
|
592
|
+
};
|
|
593
|
+
const getCellClass = (day, slot) => {
|
|
594
|
+
const selected = hasHour(value, day, slot);
|
|
595
|
+
const slotDisabled = isSlotDisabled(disabledSlots, day, slot);
|
|
596
|
+
const highlighted = cornerHovered || hoveredDay === day || hoveredHour === slot;
|
|
597
|
+
return [
|
|
598
|
+
"rsp-cell",
|
|
599
|
+
selected && "rsp-cell--selected",
|
|
600
|
+
slotDisabled && "rsp-cell--disabled",
|
|
601
|
+
highlighted && !selected && !slotDisabled && "rsp-cell--highlighted",
|
|
602
|
+
readOnly && "rsp-cell--readonly"
|
|
603
|
+
].filter(Boolean).join(" ");
|
|
604
|
+
};
|
|
605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
606
|
+
"div",
|
|
607
|
+
{
|
|
608
|
+
className: `rsp-container${disabled ? " rsp-container--disabled" : ""}${className ? ` ${className}` : ""}`,
|
|
609
|
+
onMouseUp: handlePointerUp,
|
|
610
|
+
onMouseLeave: handlePointerUp,
|
|
611
|
+
onTouchEnd: handlePointerUp,
|
|
612
|
+
children: [
|
|
613
|
+
!hideToolbar && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rsp-toolbar", children: [
|
|
614
|
+
activePresets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
615
|
+
"button",
|
|
616
|
+
{
|
|
617
|
+
type: "button",
|
|
618
|
+
className: "rsp-preset-button",
|
|
619
|
+
onClick: () => handlePreset(preset),
|
|
620
|
+
disabled: readOnly,
|
|
621
|
+
children: preset.label
|
|
622
|
+
},
|
|
623
|
+
preset.label
|
|
624
|
+
)),
|
|
625
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
626
|
+
"button",
|
|
627
|
+
{
|
|
628
|
+
type: "button",
|
|
629
|
+
className: "rsp-preset-button rsp-toolbar-clear",
|
|
630
|
+
onClick: () => onChange({}),
|
|
631
|
+
disabled: readOnly,
|
|
632
|
+
children: resolvedLocale.messages.clear
|
|
633
|
+
}
|
|
634
|
+
)
|
|
635
|
+
] }),
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
637
|
+
"div",
|
|
638
|
+
{
|
|
639
|
+
className: "rsp-table-wrapper",
|
|
640
|
+
onTouchMove: handleTouchMove,
|
|
641
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { className: "rsp-table", ref: tableRef, children: dayAxis === "y" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("colgroup", { children: [
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", { className: "rsp-day-col" }),
|
|
644
|
+
slots.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", {}, s))
|
|
645
|
+
] }),
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "rsp-header-row", onMouseLeave: () => setHoveredHour(null), children: [
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
648
|
+
"th",
|
|
649
|
+
{
|
|
650
|
+
className: `rsp-corner-cell${readOnly ? "" : " rsp-corner-cell--clickable"}`,
|
|
651
|
+
onClick: handleToggleAll,
|
|
652
|
+
onMouseEnter: () => setCornerHovered(true),
|
|
653
|
+
onMouseLeave: () => setCornerHovered(false),
|
|
654
|
+
onTouchEnd: (e) => {
|
|
655
|
+
e.preventDefault();
|
|
656
|
+
handleToggleAll();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
),
|
|
660
|
+
slots.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
661
|
+
"th",
|
|
662
|
+
{
|
|
663
|
+
"data-hour-header": s,
|
|
664
|
+
className: `rsp-header-cell${readOnly ? " rsp-header-cell--readonly" : ""}`,
|
|
665
|
+
onMouseDown: (e) => {
|
|
666
|
+
e.preventDefault();
|
|
667
|
+
handleHourHeaderDown(s);
|
|
668
|
+
},
|
|
669
|
+
onMouseOver: () => {
|
|
670
|
+
setHoveredHour(s);
|
|
671
|
+
handleHourHeaderOver(s);
|
|
672
|
+
},
|
|
673
|
+
onMouseEnter: () => setHoveredHour(s),
|
|
674
|
+
onTouchStart: (e) => {
|
|
675
|
+
e.preventDefault();
|
|
676
|
+
handleHourHeaderDown(s);
|
|
677
|
+
},
|
|
678
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: getHourLabel(s) })
|
|
679
|
+
},
|
|
680
|
+
s
|
|
681
|
+
))
|
|
682
|
+
] }) }),
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { onMouseLeave: () => setHoveredDay(null), children: visibleDays.map((day) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "rsp-day-row", children: [
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
685
|
+
"td",
|
|
686
|
+
{
|
|
687
|
+
"data-day-header": day,
|
|
688
|
+
className: getDayLabelClass(day),
|
|
689
|
+
style: getDayLabelStyle(day),
|
|
690
|
+
onMouseDown: (e) => {
|
|
691
|
+
e.preventDefault();
|
|
692
|
+
handleDayHeaderDown(day);
|
|
693
|
+
},
|
|
694
|
+
onMouseOver: () => {
|
|
695
|
+
setHoveredDay(day);
|
|
696
|
+
handleDayHeaderOver(day);
|
|
697
|
+
},
|
|
698
|
+
onMouseEnter: () => setHoveredDay(day),
|
|
699
|
+
onMouseLeave: () => setHoveredDay(null),
|
|
700
|
+
onTouchStart: (e) => {
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
handleDayHeaderDown(day);
|
|
703
|
+
},
|
|
704
|
+
children: dayLabels[day] ?? day
|
|
705
|
+
}
|
|
706
|
+
),
|
|
707
|
+
slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
708
|
+
"td",
|
|
709
|
+
{
|
|
710
|
+
className: getCellClass(day, slot),
|
|
711
|
+
"data-day": day,
|
|
712
|
+
"data-hour": slot,
|
|
713
|
+
onMouseDown: (e) => {
|
|
714
|
+
e.preventDefault();
|
|
715
|
+
handleCellPointerDown(day, slot);
|
|
716
|
+
},
|
|
717
|
+
onMouseOver: () => handleCellPointerOver(day, slot),
|
|
718
|
+
onTouchStart: (e) => {
|
|
719
|
+
e.preventDefault();
|
|
720
|
+
handleCellPointerDown(day, slot);
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
slot
|
|
724
|
+
))
|
|
725
|
+
] }, day)) })
|
|
726
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("colgroup", { children: [
|
|
728
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", { className: "rsp-hour-col" }),
|
|
729
|
+
visibleDays.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", {}, d))
|
|
730
|
+
] }),
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "rsp-header-row", onMouseLeave: () => setHoveredDay(null), children: [
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
733
|
+
"th",
|
|
734
|
+
{
|
|
735
|
+
className: `rsp-corner-cell${readOnly ? "" : " rsp-corner-cell--clickable"}`,
|
|
736
|
+
onClick: handleToggleAll,
|
|
737
|
+
onMouseEnter: () => setCornerHovered(true),
|
|
738
|
+
onMouseLeave: () => setCornerHovered(false),
|
|
739
|
+
onTouchEnd: (e) => {
|
|
740
|
+
e.preventDefault();
|
|
741
|
+
handleToggleAll();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
visibleDays.map((day) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
746
|
+
"th",
|
|
747
|
+
{
|
|
748
|
+
"data-day-header": day,
|
|
749
|
+
className: `rsp-header-cell rsp-header-cell--day${readOnly ? " rsp-header-cell--readonly" : ""}`,
|
|
750
|
+
style: getDayLabelStyle(day),
|
|
751
|
+
onMouseDown: (e) => {
|
|
752
|
+
e.preventDefault();
|
|
753
|
+
handleDayHeaderDown(day);
|
|
754
|
+
},
|
|
755
|
+
onMouseOver: () => {
|
|
756
|
+
setHoveredDay(day);
|
|
757
|
+
handleDayHeaderOver(day);
|
|
758
|
+
},
|
|
759
|
+
onMouseEnter: () => setHoveredDay(day),
|
|
760
|
+
onTouchStart: (e) => {
|
|
761
|
+
e.preventDefault();
|
|
762
|
+
handleDayHeaderDown(day);
|
|
763
|
+
},
|
|
764
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: dayLabels[day] ?? day })
|
|
765
|
+
},
|
|
766
|
+
day
|
|
767
|
+
))
|
|
768
|
+
] }) }),
|
|
769
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { onMouseLeave: () => setHoveredHour(null), children: slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "rsp-day-row", children: [
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
771
|
+
"td",
|
|
772
|
+
{
|
|
773
|
+
"data-hour-header": slot,
|
|
774
|
+
className: `rsp-hour-label${readOnly ? " rsp-hour-label--readonly" : ""}`,
|
|
775
|
+
onMouseDown: (e) => {
|
|
776
|
+
e.preventDefault();
|
|
777
|
+
handleHourHeaderDown(slot);
|
|
778
|
+
},
|
|
779
|
+
onMouseOver: () => {
|
|
780
|
+
setHoveredHour(slot);
|
|
781
|
+
handleHourHeaderOver(slot);
|
|
782
|
+
},
|
|
783
|
+
onMouseEnter: () => setHoveredHour(slot),
|
|
784
|
+
onMouseLeave: () => setHoveredHour(null),
|
|
785
|
+
onTouchStart: (e) => {
|
|
786
|
+
e.preventDefault();
|
|
787
|
+
handleHourHeaderDown(slot);
|
|
788
|
+
},
|
|
789
|
+
children: getHourLabel(slot)
|
|
790
|
+
}
|
|
791
|
+
),
|
|
792
|
+
visibleDays.map((day) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
793
|
+
"td",
|
|
794
|
+
{
|
|
795
|
+
className: getCellClass(day, slot),
|
|
796
|
+
"data-day": day,
|
|
797
|
+
"data-hour": slot,
|
|
798
|
+
onMouseDown: (e) => {
|
|
799
|
+
e.preventDefault();
|
|
800
|
+
handleCellPointerDown(day, slot);
|
|
801
|
+
},
|
|
802
|
+
onMouseOver: () => handleCellPointerOver(day, slot),
|
|
803
|
+
onTouchStart: (e) => {
|
|
804
|
+
e.preventDefault();
|
|
805
|
+
handleCellPointerDown(day, slot);
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
day
|
|
809
|
+
))
|
|
810
|
+
] }, slot)) })
|
|
811
|
+
] }) })
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
]
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/serialize.ts
|
|
820
|
+
var DAY_TO_ISO = {
|
|
821
|
+
mon: 1,
|
|
822
|
+
tue: 2,
|
|
823
|
+
wed: 3,
|
|
824
|
+
thu: 4,
|
|
825
|
+
fri: 5,
|
|
826
|
+
sat: 6,
|
|
827
|
+
sun: 7
|
|
828
|
+
};
|
|
829
|
+
var ISO_TO_DAY = {
|
|
830
|
+
1: "mon",
|
|
831
|
+
2: "tue",
|
|
832
|
+
3: "wed",
|
|
833
|
+
4: "thu",
|
|
834
|
+
5: "fri",
|
|
835
|
+
6: "sat",
|
|
836
|
+
7: "sun"
|
|
837
|
+
};
|
|
838
|
+
function pad2(n) {
|
|
839
|
+
return String(n).padStart(2, "0");
|
|
840
|
+
}
|
|
841
|
+
function hourToTimeString(hour) {
|
|
842
|
+
return `${pad2(hour)}:00`;
|
|
843
|
+
}
|
|
844
|
+
function compressHours(hours) {
|
|
845
|
+
if (hours.length === 0) return [];
|
|
846
|
+
const sorted = [...hours].sort((a, b) => a - b);
|
|
847
|
+
const out = [];
|
|
848
|
+
let start = sorted[0];
|
|
849
|
+
let prev = sorted[0];
|
|
850
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
851
|
+
const cur = sorted[i];
|
|
852
|
+
if (cur === prev + 1) {
|
|
853
|
+
prev = cur;
|
|
854
|
+
} else {
|
|
855
|
+
out.push({ start, end: prev + 1 });
|
|
856
|
+
start = cur;
|
|
857
|
+
prev = cur;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
out.push({ start, end: prev + 1 });
|
|
861
|
+
return out;
|
|
862
|
+
}
|
|
863
|
+
function toRanges(schedule, options = {}) {
|
|
864
|
+
const ranges = [];
|
|
865
|
+
for (const dayKey of DAY_ORDER) {
|
|
866
|
+
const iso = DAY_TO_ISO[dayKey];
|
|
867
|
+
if (!iso) continue;
|
|
868
|
+
const hours = schedule[dayKey];
|
|
869
|
+
if (!hours || hours.length === 0) continue;
|
|
870
|
+
for (const { start, end } of compressHours(hours)) {
|
|
871
|
+
ranges.push({
|
|
872
|
+
day: iso,
|
|
873
|
+
start: hourToTimeString(start),
|
|
874
|
+
end: end >= 24 ? "24:00" : hourToTimeString(end)
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const payload = { version: 1, ranges };
|
|
879
|
+
if (options.timezone) payload.timezone = options.timezone;
|
|
880
|
+
if (options.meta) payload.meta = options.meta;
|
|
881
|
+
return payload;
|
|
882
|
+
}
|
|
883
|
+
function parseTimeString(s) {
|
|
884
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(s);
|
|
885
|
+
if (!m) throw new Error(`Invalid time string: ${s}`);
|
|
886
|
+
const h = Number(m[1]);
|
|
887
|
+
const min = Number(m[2]);
|
|
888
|
+
if (min !== 0) {
|
|
889
|
+
throw new Error(
|
|
890
|
+
`fromRanges only supports hour-aligned times (got ${s}). Sub-hour granularity is not supported in current version.`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
if (h < 0 || h > 24) throw new Error(`Hour out of range: ${h}`);
|
|
894
|
+
return h;
|
|
895
|
+
}
|
|
896
|
+
function fromRanges(payload) {
|
|
897
|
+
const schedule = {};
|
|
898
|
+
for (const range of payload.ranges) {
|
|
899
|
+
const dayKey = ISO_TO_DAY[range.day];
|
|
900
|
+
if (!dayKey) throw new Error(`Invalid ISO day: ${range.day}`);
|
|
901
|
+
const start = parseTimeString(range.start);
|
|
902
|
+
const end = parseTimeString(range.end);
|
|
903
|
+
if (end <= start) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`Range end (${range.end}) must be after start (${range.start})`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
const hours = schedule[dayKey] ?? [];
|
|
909
|
+
for (let h = start; h < end; h++) {
|
|
910
|
+
if (!hours.includes(h)) hours.push(h);
|
|
911
|
+
}
|
|
912
|
+
hours.sort((a, b) => a - b);
|
|
913
|
+
schedule[dayKey] = hours;
|
|
914
|
+
}
|
|
915
|
+
return schedule;
|
|
916
|
+
}
|
|
917
|
+
function toISO(schedule, options = {}) {
|
|
918
|
+
const payload = toRanges(schedule, options);
|
|
919
|
+
const availability = payload.ranges.map((r) => ({
|
|
920
|
+
day: r.day,
|
|
921
|
+
startTime: r.start,
|
|
922
|
+
endTime: r.end
|
|
923
|
+
}));
|
|
924
|
+
const out = { version: 1, availability };
|
|
925
|
+
if (options.timezone) out.timezone = options.timezone;
|
|
926
|
+
if (options.meta) out.meta = options.meta;
|
|
927
|
+
return out;
|
|
928
|
+
}
|
|
929
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
930
|
+
0 && (module.exports = {
|
|
931
|
+
DAY_LABELS,
|
|
932
|
+
DAY_ORDER,
|
|
933
|
+
DEFAULT_PRESETS,
|
|
934
|
+
HOURS,
|
|
935
|
+
LOCALE_PRESETS,
|
|
936
|
+
SchedulePicker,
|
|
937
|
+
applyRect,
|
|
938
|
+
countSelectedHours,
|
|
939
|
+
createEmptySchedule,
|
|
940
|
+
createFullSchedule,
|
|
941
|
+
formatHourHeader,
|
|
942
|
+
fromRanges,
|
|
943
|
+
generateSlots,
|
|
944
|
+
getDefaultPresets,
|
|
945
|
+
hasHour,
|
|
946
|
+
isAllSelected,
|
|
947
|
+
isSlotDisabled,
|
|
948
|
+
resolveLocaleConfig,
|
|
949
|
+
rotateDays,
|
|
950
|
+
slotToTimeString,
|
|
951
|
+
summarizeSchedule,
|
|
952
|
+
toISO,
|
|
953
|
+
toRanges,
|
|
954
|
+
toggleHour
|
|
955
|
+
});
|
|
956
|
+
//# sourceMappingURL=index.js.map
|