podo-ui 0.8.1 → 0.8.2

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Podo UI DatePicker CSS v0.8.1
2
+ * Podo UI DatePicker CSS v0.8.2
3
3
  * https://podoui.com
4
4
  * MIT License
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Podo UI DatePicker v0.8.1
2
+ * Podo UI DatePicker v0.8.2
3
3
  * https://podoui.com
4
4
  * MIT License
5
5
  */
@@ -37,6 +37,66 @@
37
37
  // Helper Functions
38
38
  // ============================================
39
39
 
40
+ /**
41
+ * CalendarInitial 값을 Date로 변환
42
+ * @param {string|Date|undefined} initial - 'now', 'prevMonth', 'nextMonth', or Date
43
+ * @param {Date} fallback - 기본값
44
+ * @returns {Date}
45
+ */
46
+ function resolveCalendarInitial(initial, fallback) {
47
+ if (!initial) return fallback;
48
+ if (initial instanceof Date) return initial;
49
+
50
+ const now = new Date();
51
+ switch (initial) {
52
+ case 'now':
53
+ return new Date(now.getFullYear(), now.getMonth(), 1);
54
+ case 'prevMonth':
55
+ return new Date(now.getFullYear(), now.getMonth() - 1, 1);
56
+ case 'nextMonth':
57
+ return new Date(now.getFullYear(), now.getMonth() + 1, 1);
58
+ default:
59
+ return fallback;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 커스텀 포맷으로 날짜/시간 포맷팅
65
+ * y: 년(4자리), m: 월(2자리), d: 일(2자리), h: 시(2자리), i: 분(2자리)
66
+ * @param {Date|undefined} date
67
+ * @param {Object|undefined} time - { hour, minute }
68
+ * @param {string} pattern
69
+ * @returns {string}
70
+ */
71
+ function formatWithPattern(date, time, pattern) {
72
+ if (!date && !time) return '';
73
+
74
+ let result = pattern;
75
+
76
+ if (date) {
77
+ result = result.replace(/y/g, String(date.getFullYear()));
78
+ result = result.replace(/m/g, String(date.getMonth() + 1).padStart(2, '0'));
79
+ result = result.replace(/d/g, String(date.getDate()).padStart(2, '0'));
80
+ }
81
+
82
+ if (time) {
83
+ result = result.replace(/h/g, String(time.hour).padStart(2, '0'));
84
+ result = result.replace(/i/g, String(time.minute).padStart(2, '0'));
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ /**
91
+ * 날짜만 표시하는 포맷 추출 (시간 부분 제거)
92
+ * @param {string|undefined} format
93
+ * @returns {string|undefined}
94
+ */
95
+ function getDateOnlyFormat(format) {
96
+ if (!format) return undefined;
97
+ return format.replace(/\s*h[:\s]*i[분]?/g, '').replace(/\s*h시\s*i분/g, '').trim();
98
+ }
99
+
40
100
  function formatDate(date) {
41
101
  const year = date.getFullYear();
42
102
  const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -171,6 +231,8 @@
171
231
  * @param {Date|Object} [options.maxDate] - Maximum selectable date
172
232
  * @param {number} [options.minuteStep=1] - Minute step (1, 5, 10, 15, 20, 30)
173
233
  * @param {Object} [options.texts] - Custom texts for localization
234
+ * @param {string} [options.format] - Date/time format (y: year, m: month, d: day, h: hour, i: minute)
235
+ * @param {Object} [options.initialCalendar] - Initial calendar display month { start, end }
174
236
  */
175
237
  constructor(container, options = {}) {
176
238
  this.container =
@@ -196,20 +258,37 @@
196
258
  this.maxDate = options.maxDate;
197
259
  this.minuteStep = options.minuteStep || 1;
198
260
  this.texts = { ...DEFAULT_TEXTS, ...options.texts };
261
+ this.format = options.format;
262
+ this.initialCalendar = options.initialCalendar || {};
199
263
 
200
264
  // State
201
265
  this.isOpen = false;
202
266
  this.selectingPart = null;
203
- this.viewDate = this.value.date ? new Date(this.value.date) : new Date();
204
- this.endViewDate = new Date(
205
- this.value.endDate
206
- ? this.value.endDate.getFullYear()
207
- : this.viewDate.getFullYear(),
208
- this.value.endDate
209
- ? this.value.endDate.getMonth() + 1
210
- : this.viewDate.getMonth() + 1,
211
- 1
212
- );
267
+
268
+ // 초기 달력 표시 월 계산
269
+ if (this.value.date) {
270
+ this.viewDate = new Date(this.value.date);
271
+ } else if (this.initialCalendar.start) {
272
+ this.viewDate = resolveCalendarInitial(this.initialCalendar.start, new Date());
273
+ } else {
274
+ this.viewDate = new Date();
275
+ }
276
+
277
+ if (this.value.endDate) {
278
+ this.endViewDate = new Date(
279
+ this.value.endDate.getFullYear(),
280
+ this.value.endDate.getMonth() + 1,
281
+ 1
282
+ );
283
+ } else if (this.initialCalendar.end) {
284
+ this.endViewDate = resolveCalendarInitial(this.initialCalendar.end, new Date());
285
+ } else {
286
+ this.endViewDate = new Date(
287
+ this.viewDate.getFullYear(),
288
+ this.viewDate.getMonth() + 1,
289
+ 1
290
+ );
291
+ }
213
292
 
214
293
  // Build UI
215
294
  this.render();
@@ -315,11 +394,22 @@
315
394
  createDateButton(date, part) {
316
395
  const btn = createElement('button', `${PREFIX}__part`);
317
396
  btn.type = 'button';
397
+
398
+ const dateFormat = getDateOnlyFormat(this.format);
399
+
318
400
  if (!date) {
319
401
  btn.classList.add(`${PREFIX}__part--placeholder`);
320
- btn.textContent = 'YYYY - MM - DD';
402
+ // format이 있으면 placeholder도 포맷에 맞게 표시
403
+ const placeholderText = dateFormat
404
+ ? dateFormat.replace(/y/g, 'YYYY').replace(/m/g, 'MM').replace(/d/g, 'DD')
405
+ : 'YYYY - MM - DD';
406
+ btn.textContent = placeholderText;
321
407
  } else {
322
- btn.textContent = formatDate(date);
408
+ // format prop이 있으면 사용 (날짜만)
409
+ const displayText = dateFormat
410
+ ? formatWithPattern(date, null, dateFormat)
411
+ : formatDate(date);
412
+ btn.textContent = displayText;
323
413
  }
324
414
  btn.dataset.part = part;
325
415
  return btn;
@@ -729,6 +819,17 @@
729
819
  formatPeriodText() {
730
820
  if (!this.tempValue.date) return '';
731
821
 
822
+ // format prop이 있으면 사용
823
+ if (this.format) {
824
+ const startText = formatWithPattern(this.tempValue.date, this.tempValue.time, this.format);
825
+ if (this.tempValue.endDate) {
826
+ const endText = formatWithPattern(this.tempValue.endDate, this.tempValue.endTime, this.format);
827
+ return `${startText} ~ ${endText}`;
828
+ }
829
+ return startText;
830
+ }
831
+
832
+ // 기본 포맷 (한국어)
732
833
  const formatKoreanDateTime = (date, time) => {
733
834
  const year = date.getFullYear();
734
835
  const month = date.getMonth() + 1;
@@ -1,2 +1,2 @@
1
- /*! Podo UI DatePicker CSS v0.8.1 | MIT License | https://podoui.com */
1
+ /*! Podo UI DatePicker CSS v0.8.2 | MIT License | https://podoui.com */
2
2
  .podo-datepicker{--podo-bg-block:var(--color-bg-block,#ffffff);--podo-bg-modal:var(--color-bg-modal,#ffffff);--podo-bg-disabled:var(--color-bg-disabled,#f5f5f5);--podo-border:var(--color-border,#e0e0e0);--podo-border-hover:var(--color-border-hover,#bdbdbd);--podo-text-body:var(--color-text-body,#212121);--podo-text-sub:var(--color-text-sub,#757575);--podo-text-disabled:var(--color-text-disabled,#9e9e9e);--podo-primary:var(--color-primary,#7c3aed);--podo-primary-hover:var(--color-primary-hover,#6d28d9);--podo-primary-fill:var(--color-primary-fill,rgba(124,58,237,0.1));--podo-primary-reverse:var(--color-primary-reverse,#ffffff);--podo-default:var(--color-default,#f5f5f5);--podo-default-fill:var(--color-default-fill,#eeeeee);--podo-default-hover:var(--color-default-hover,#e0e0e0);--podo-default-reverse:var(--color-default-reverse,#212121);--podo-radius-sm:var(--radius-2,4px);--podo-radius-md:var(--radius-3,8px);--podo-spacing-1:var(--spacing-1,4px);--podo-spacing-2:var(--spacing-2,8px);--podo-spacing-3:var(--spacing-3,12px);--podo-spacing-4:var(--spacing-4,16px);--podo-spacing-5:var(--spacing-5,20px);--podo-font-size-sm:13px;--podo-font-size-md:14px;--podo-font-size-lg:16px;}.podo-datepicker{position:relative;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-size:var(--podo-font-size-md);line-height:1.5;}.podo-datepicker__input{display:flex;align-items:center;gap:var(--podo-spacing-2);padding:var(--podo-spacing-3) var(--podo-spacing-4);background:var(--podo-bg-block);border:1px solid var(--podo-border);border-radius:var(--podo-radius-md);cursor:pointer;transition:border-color 0.2s;}.podo-datepicker__input:hover{border-color:var(--podo-border-hover);}.podo-datepicker__input--active{border-color:var(--podo-primary);}.podo-datepicker__input--disabled{background:var(--podo-bg-disabled);cursor:not-allowed;opacity:0.6;}.podo-datepicker__input-content{flex:1;display:flex;align-items:center;gap:var(--podo-spacing-2);}.podo-datepicker__part{display:flex;flex:1;align-items:center;justify-content:center;padding:0 var(--podo-spacing-2);background:none;border:none;border-radius:var(--podo-radius-sm);cursor:pointer;transition:background 0.2s;color:var(--podo-text-body);white-space:nowrap;font-variant-numeric:tabular-nums;font-family:inherit;font-size:var(--podo-font-size-md);}.podo-datepicker__part:hover{background:var(--podo-default);}.podo-datepicker__part--active{background:var(--podo-default-fill);}.podo-datepicker__part--placeholder{color:var(--podo-text-disabled);}.podo-datepicker__separator{color:var(--podo-text-body);padding:0 var(--podo-spacing-1);font-size:var(--podo-font-size-md);}.podo-datepicker__time-section{display:flex;align-items:center;justify-content:center;gap:0;}.podo-datepicker__time-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;background:none;border:none;padding:0 var(--podo-spacing-2);flex:1;text-align:center;text-align-last:center;color:var(--podo-text-body);cursor:pointer;border-radius:var(--podo-radius-sm);transition:background 0.2s;font-variant-numeric:tabular-nums;font-family:inherit;font-size:var(--podo-font-size-md);}.podo-datepicker__time-select:hover{background:var(--podo-default);}.podo-datepicker__time-select:focus{outline:none;background:var(--podo-default-fill);}.podo-datepicker__time-select--placeholder{color:var(--podo-text-disabled);}.podo-datepicker__time-select:disabled{cursor:not-allowed;opacity:0.6;}.podo-datepicker__time-separator{color:var(--podo-text-body);font-size:var(--podo-font-size-md);}.podo-datepicker__icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;font-size:20px;line-height:1;color:var(--podo-text-sub);flex-shrink:0;}.podo-datepicker__dropdown{position:absolute;top:calc(100%+var(--podo-spacing-2));left:0;z-index:1000;display:flex;flex-direction:column;gap:10px;padding:var(--podo-spacing-5);background:var(--podo-bg-modal);border-radius:var(--podo-radius-md);box-shadow:0 6px 18px -3px rgba(50,50,50,0.12);}.podo-datepicker__dropdown--right{left:auto;right:0;}.podo-datepicker__calendar{display:flex;flex-direction:column;gap:10px;min-width:280px;}.podo-datepicker__calendar-nav{display:flex;align-items:center;gap:var(--podo-spacing-4);height:40px;}.podo-datepicker__nav-button{display:flex;align-items:center;justify-content:center;width:40px;height:40px;background:var(--podo-default);border:none;border-radius:var(--podo-radius-md);cursor:pointer;transition:background 0.2s;}.podo-datepicker__nav-button:hover{background:var(--podo-default-hover);}.podo-datepicker__nav-button:disabled{opacity:0.5;cursor:not-allowed;}.podo-datepicker__nav-button i{font-size:20px;color:var(--podo-text-body);}.podo-datepicker__nav-title{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--podo-spacing-1);}.podo-datepicker__nav-select-wrapper{position:relative;display:flex;align-items:center;}.podo-datepicker__nav-select-wrapper::after{content:'';position:absolute;right:var(--podo-spacing-2);top:50%;transform:translateY(-50%);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid var(--podo-text-body);pointer-events:none;}.podo-datepicker__nav-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;background:none;border:none;padding:var(--podo-spacing-2) 24px var(--podo-spacing-2) var(--podo-spacing-2);border-radius:var(--podo-radius-sm);cursor:pointer;transition:background 0.2s;color:var(--podo-text-body);font-family:inherit;font-size:var(--podo-font-size-lg);font-weight:600;}.podo-datepicker__nav-select:hover{background:var(--podo-default);}.podo-datepicker__nav-select:focus{outline:none;background:var(--podo-default-fill);}.podo-datepicker__calendar-grid{display:flex;flex-direction:column;gap:var(--podo-spacing-2);padding-top:var(--podo-spacing-3);border-top:1px solid var(--podo-border);}.podo-datepicker__calendar-row{display:flex;width:100%;}.podo-datepicker__calendar-cell{flex:1;display:flex;align-items:center;justify-content:center;height:40px;font-size:var(--podo-font-size-md);background:none;border:none;border-radius:var(--podo-radius-md);cursor:pointer;transition:background 0.2s;font-family:inherit;color:var(--podo-text-body);}.podo-datepicker__calendar-cell--header{color:var(--podo-text-sub);cursor:default;font-weight:500;}.podo-datepicker__calendar-cell--other{color:var(--podo-text-disabled);}.podo-datepicker__calendar-cell--today{font-weight:600;color:var(--podo-primary);}.podo-datepicker__calendar-cell--selected{background:var(--podo-primary);color:var(--podo-primary-reverse);font-weight:600;}.podo-datepicker__calendar-cell--in-range{background:var(--podo-primary-fill);border-radius:0;}.podo-datepicker__calendar-cell--range-start{background:var(--podo-primary);color:var(--podo-primary-reverse);font-weight:600;border-radius:var(--podo-radius-md);}.podo-datepicker__calendar-cell--range-end{background:var(--podo-primary);color:var(--podo-primary-reverse);font-weight:600;border-radius:var(--podo-radius-md);}.podo-datepicker__calendar-cell--disabled{color:var(--podo-text-disabled);background:var(--podo-default);border:none;border-radius:0;outline:none;cursor:not-allowed;}.podo-datepicker__calendar-cell--disabled:hover{background:var(--podo-default);}.podo-datepicker__calendar-cell:not(.podo-datepicker__calendar-cell--header):not(.podo-datepicker__calendar-cell--selected):not(.podo-datepicker__calendar-cell--disabled):not(.podo-datepicker__calendar-cell--range-start):not(.podo-datepicker__calendar-cell--range-end):not(.podo-datepicker__calendar-cell--in-range):hover{background:var(--podo-default);}.podo-datepicker__period-calendars{display:flex;gap:var(--podo-spacing-4);}.podo-datepicker__period-calendar-left,.podo-datepicker__period-calendar-right{flex:1;}.podo-datepicker__actions{display:flex;align-items:center;justify-content:space-between;gap:var(--podo-spacing-3);padding-top:var(--podo-spacing-3);border-top:1px solid var(--podo-border);}.podo-datepicker__period-text{font-size:var(--podo-font-size-md);color:var(--podo-primary);flex:1;}.podo-datepicker__action-buttons{display:flex;gap:var(--podo-spacing-3);flex-shrink:0;}.podo-datepicker__action-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--podo-spacing-2);padding:var(--podo-spacing-3) var(--podo-spacing-4);border-radius:var(--podo-radius-md);cursor:pointer;transition:background 0.2s;white-space:nowrap;font-family:inherit;font-size:var(--podo-font-size-md);}.podo-datepicker__action-button i{display:flex;align-items:center;justify-content:center;width:18px;height:18px;font-size:18px;line-height:1;}.podo-datepicker__action-button--reset{background:var(--podo-default-fill);border:1px solid var(--podo-border);color:var(--podo-default-reverse);}.podo-datepicker__action-button--reset:hover{background:var(--podo-default-hover);}.podo-datepicker__action-button--apply{background:var(--podo-primary);border:none;color:var(--podo-primary-reverse);}.podo-datepicker__action-button--apply:hover{background:var(--podo-primary-hover);}
@@ -1,2 +1,2 @@
1
- /*! Podo UI DatePicker v0.8.1 | MIT License | https://podoui.com */
2
- (function (global, factory) {typeof exports === 'object' && typeof module !== 'undefined'? (module.exports = factory()): typeof define === 'function' && define.amd? define(factory): ((global = typeof globalThis !== 'undefined' ? globalThis : global || self),(global.PodoDatePicker = factory()));})(this, function () {'use strict';const PREFIX = 'podo-datepicker';const DEFAULT_TEXTS = {weekDays: ['일', '월', '화', '수', '목', '금', '토'],months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],yearSuffix: '년',reset: '초기화',apply: '적용',};function formatDate(date) {const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0');const day = String(date.getDate()).padStart(2, '0');return `${year} - ${month} - ${day}`;}function formatTime(hour, minute) {return `${String(hour).padStart(2, '0')} : ${String(minute).padStart(2, '0')}`;}function isSameDay(date1, date2) {if (!date1 || !date2) return false;return (date1.getFullYear() === date2.getFullYear() &&date1.getMonth() === date2.getMonth() &&date1.getDate() === date2.getDate());}function isInRange(date, start, end) {const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const s = new Date(start.getFullYear(), start.getMonth(), start.getDate());const e = new Date(end.getFullYear(), end.getMonth(), end.getDate());return d >= s && d <= e;}function isInRangeExclusive(date, start, end) {const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const s = new Date(start.getFullYear(), start.getMonth(), start.getDate());const e = new Date(end.getFullYear(), end.getMonth(), end.getDate());return d > s && d < e;}function getDaysInMonth(year, month) {return new Date(year, month + 1, 0).getDate();}function getFirstDayOfMonth(year, month) {return new Date(year, month, 1).getDay();}function isDateRange(condition) {return typeof condition === 'object' && condition !== null && 'from' in condition && 'to' in condition;}function matchesCondition(date, condition) {if (typeof condition === 'function') {return condition(date);}if (isDateRange(condition)) {return isInRange(date, condition.from, condition.to);}return isSameDay(date, condition);}function isDateDisabled(date, disable, enable) {if (enable && enable.length > 0) {const isEnabled = enable.some((condition) => matchesCondition(date, condition));return !isEnabled;}if (disable && disable.length > 0) {return disable.some((condition) => matchesCondition(date, condition));}return false;}function isDateTimeLimit(value) {return typeof value === 'object' && value !== null && 'date' in value && !(value instanceof Date);}function extractDateTimeLimit(limit) {if (isDateTimeLimit(limit)) {return { date: limit.date, time: limit.time };}return { date: limit };}function isBeforeMinDate(date, minDate) {if (!minDate) return false;const { date: minDateValue } = extractDateTimeLimit(minDate);const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const m = new Date(minDateValue.getFullYear(), minDateValue.getMonth(), minDateValue.getDate());return d < m;}function isAfterMaxDate(date, maxDate) {if (!maxDate) return false;const { date: maxDateValue } = extractDateTimeLimit(maxDate);const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const m = new Date(maxDateValue.getFullYear(), maxDateValue.getMonth(), maxDateValue.getDate());return d > m;}function createElement(tag, className, content) {const el = document.createElement(tag);if (className) el.className = className;if (content !== undefined) {if (typeof content === 'string' || typeof content === 'number') {el.textContent = content;} else if (content instanceof HTMLElement) {el.appendChild(content);}}return el;}class PodoDatePicker {constructor(container, options = {}) {this.container =typeof container === 'string' ? document.querySelector(container) : container;if (!this.container) {throw new Error('PodoDatePicker: Container element not found');}this.mode = options.mode || 'instant';this.type = options.type || 'date';this.value = options.value || {};this.tempValue = { ...this.value };this.onChange = options.onChange;this.placeholder = options.placeholder;this.disabled = options.disabled || false;this.showActions = options.showActions ?? this.mode === 'period';this.align = options.align || 'left';this.disable = options.disable || [];this.enable = options.enable || [];this.minDate = options.minDate;this.maxDate = options.maxDate;this.minuteStep = options.minuteStep || 1;this.texts = { ...DEFAULT_TEXTS, ...options.texts };this.isOpen = false;this.selectingPart = null;this.viewDate = this.value.date ? new Date(this.value.date) : new Date();this.endViewDate = new Date(this.value.endDate? this.value.endDate.getFullYear(): this.viewDate.getFullYear(),this.value.endDate? this.value.endDate.getMonth() + 1: this.viewDate.getMonth() + 1,1);this.render();this.bindEvents();}render() {this.container.innerHTML = '';this.container.className = PREFIX;this.inputEl = createElement('div', `${PREFIX}__input`);if (this.disabled) this.inputEl.classList.add(`${PREFIX}__input--disabled`);this.inputContentEl = createElement('div', `${PREFIX}__input-content`);this.renderInputContent();this.inputEl.appendChild(this.inputContentEl);const iconClass = this.type === 'time' ? 'icon-time' : 'icon-calendar';this.iconEl = createElement('i', `${PREFIX}__icon ${iconClass}`);this.inputEl.appendChild(this.iconEl);this.container.appendChild(this.inputEl);this.dropdownEl = createElement('div',`${PREFIX}__dropdown ${this.align === 'right' ? `${PREFIX}__dropdown--right` : ''}`);this.dropdownEl.style.display = 'none';this.container.appendChild(this.dropdownEl);}renderInputContent() {this.inputContentEl.innerHTML = '';const displayValue = this.showActions ? this.tempValue : this.value;if (this.type === 'date') {this.renderDateInput(displayValue);} else if (this.type === 'time') {this.renderTimeInput(displayValue);} else {this.renderDateTimeInput(displayValue);}}renderDateInput(displayValue) {this.startDateBtn = this.createDateButton(displayValue.date, 'date');this.inputContentEl.appendChild(this.startDateBtn);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);this.endDateBtn = this.createDateButton(displayValue.endDate, 'endDate');this.inputContentEl.appendChild(this.endDateBtn);}}renderTimeInput(displayValue) {const startTimeSection = this.createTimeSection(displayValue.time, 'hour', 'minute');this.inputContentEl.appendChild(startTimeSection);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);const endTimeSection = this.createTimeSection(displayValue.endTime, 'endHour', 'endMinute');this.inputContentEl.appendChild(endTimeSection);}}renderDateTimeInput(displayValue) {this.startDateBtn = this.createDateButton(displayValue.date, 'date');this.inputContentEl.appendChild(this.startDateBtn);const startTimeSection = this.createTimeSection(displayValue.time, 'hour', 'minute');this.inputContentEl.appendChild(startTimeSection);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);this.endDateBtn = this.createDateButton(displayValue.endDate, 'endDate');this.inputContentEl.appendChild(this.endDateBtn);const endTimeSection = this.createTimeSection(displayValue.endTime, 'endHour', 'endMinute');this.inputContentEl.appendChild(endTimeSection);}}createDateButton(date, part) {const btn = createElement('button', `${PREFIX}__part`);btn.type = 'button';if (!date) {btn.classList.add(`${PREFIX}__part--placeholder`);btn.textContent = 'YYYY - MM - DD';} else {btn.textContent = formatDate(date);}btn.dataset.part = part;return btn;}createTimeSection(time, hourPart, minutePart) {const section = createElement('div', `${PREFIX}__time-section`);const hourSelect = this.createHourSelect(time, hourPart);section.appendChild(hourSelect);const sep = createElement('span', `${PREFIX}__time-separator`, ':');section.appendChild(sep);const minuteSelect = this.createMinuteSelect(time, minutePart);section.appendChild(minuteSelect);return section;}createHourSelect(time, part) {const select = createElement('select', `${PREFIX}__time-select`);if (!time) select.classList.add(`${PREFIX}__time-select--placeholder`);if (this.disabled) select.disabled = true;const isEnd = part === 'endHour';const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;for (let h = 0; h < 24; h++) {const opt = createElement('option', null, String(h).padStart(2, '0'));opt.value = h;if (this.isHourDisabled(h, currentDate)) {opt.disabled = true;}select.appendChild(opt);}select.value = time?.hour ?? 0;select.dataset.part = part;return select;}createMinuteSelect(time, part) {const select = createElement('select', `${PREFIX}__time-select`);if (!time) select.classList.add(`${PREFIX}__time-select--placeholder`);if (this.disabled) select.disabled = true;const isEnd = part === 'endMinute';const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;const currentTime = isEnd ? this.tempValue.endTime : this.tempValue.time;for (let m = 0; m < 60; m += this.minuteStep) {const opt = createElement('option', null, String(m).padStart(2, '0'));opt.value = m;if (this.isMinuteDisabled(m, currentDate, currentTime)) {opt.disabled = true;}select.appendChild(opt);}let minute = time?.minute ?? 0;if (minute % this.minuteStep !== 0) {minute = Math.floor(minute / this.minuteStep) * this.minuteStep;}select.value = minute;select.dataset.part = part;return select;}isHourDisabled(h, currentDate) {if (!currentDate) return false;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date)) {if (h < minLimit.time.hour) return true;}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date)) {if (h > maxLimit.time.hour) return true;}return false;}isMinuteDisabled(m, currentDate, currentTime) {if (!currentDate || !currentTime) return false;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date) && currentTime.hour === minLimit.time.hour) {if (m < minLimit.time.minute) return true;}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date) && currentTime.hour === maxLimit.time.hour) {if (m > maxLimit.time.minute) return true;}return false;}renderDropdown() {this.dropdownEl.innerHTML = '';if (this.mode === 'period') {this.renderPeriodCalendars();} else {this.renderCalendar(this.viewDate, (date) => this.handleViewDateChange(date));}if (this.showActions) {this.renderActions();}}renderCalendar(viewDate, onViewDateChange, opts = {}) {const calendar = createElement('div', `${PREFIX}__calendar`);const nav = this.renderCalendarNav(viewDate, onViewDateChange, opts);calendar.appendChild(nav);const grid = this.renderCalendarGrid(viewDate);calendar.appendChild(grid);this.dropdownEl.appendChild(calendar);return calendar;}renderPeriodCalendars() {const wrapper = createElement('div', `${PREFIX}__period-calendars`);const leftCal = createElement('div', `${PREFIX}__period-calendar-left`);const leftCalendar = this.createCalendarElement(this.viewDate,(date) => this.handleViewDateChange(date),{ maxViewDate: this.endViewDate });leftCal.appendChild(leftCalendar);wrapper.appendChild(leftCal);const rightCal = createElement('div', `${PREFIX}__period-calendar-right`);const rightCalendar = this.createCalendarElement(this.endViewDate,(date) => this.handleEndViewDateChange(date),{ minViewDate: this.viewDate });rightCal.appendChild(rightCalendar);wrapper.appendChild(rightCal);this.dropdownEl.appendChild(wrapper);}createCalendarElement(viewDate, onViewDateChange, opts = {}) {const calendar = createElement('div', `${PREFIX}__calendar`);const nav = this.renderCalendarNav(viewDate, onViewDateChange, opts);calendar.appendChild(nav);const grid = this.renderCalendarGrid(viewDate);calendar.appendChild(grid);return calendar;}renderCalendarNav(viewDate, onViewDateChange, opts = {}) {const nav = createElement('div', `${PREFIX}__calendar-nav`);const year = viewDate.getFullYear();const month = viewDate.getMonth();const minViewDate = opts.minViewDate;const maxViewDate = opts.maxViewDate;const minYear = minViewDate?.getFullYear();const minMonth = minViewDate?.getMonth();const maxYear = maxViewDate?.getFullYear();const maxMonth = maxViewDate?.getMonth();const isPrevDisabled = minViewDate? year < minYear || (year === minYear && month <= minMonth): false;const isNextDisabled = maxViewDate? year > maxYear || (year === maxYear && month >= maxMonth): false;const prevBtn = createElement('button', `${PREFIX}__nav-button`);prevBtn.type = 'button';prevBtn.innerHTML = '<i class="icon-expand-left"></i>';if (isPrevDisabled) prevBtn.disabled = true;prevBtn.addEventListener('click', () => {if (!isPrevDisabled) {onViewDateChange(new Date(year, month - 1, 1));this.renderDropdown();}});nav.appendChild(prevBtn);const title = createElement('div', `${PREFIX}__nav-title`);const yearWrapper = createElement('div', `${PREFIX}__nav-select-wrapper`);const yearSelect = createElement('select', `${PREFIX}__nav-select`);const currentYear = new Date().getFullYear();for (let y = currentYear - 10; y <= currentYear + 10; y++) {if (minYear !== undefined && y < minYear) continue;if (maxYear !== undefined && y > maxYear) continue;const opt = createElement('option', null, `${y}${this.texts.yearSuffix}`);opt.value = y;yearSelect.appendChild(opt);}yearSelect.value = year;yearSelect.addEventListener('change', (e) => {onViewDateChange(new Date(parseInt(e.target.value), month, 1));this.renderDropdown();});yearWrapper.appendChild(yearSelect);title.appendChild(yearWrapper);const monthWrapper = createElement('div', `${PREFIX}__nav-select-wrapper`);const monthSelect = createElement('select', `${PREFIX}__nav-select`);for (let m = 0; m < 12; m++) {if (minYear !== undefined && minMonth !== undefined && year === minYear && m < minMonth) continue;if (maxYear !== undefined && maxMonth !== undefined && year === maxYear && m > maxMonth) continue;const opt = createElement('option', null, this.texts.months[m]);opt.value = m;monthSelect.appendChild(opt);}monthSelect.value = month;monthSelect.addEventListener('change', (e) => {onViewDateChange(new Date(year, parseInt(e.target.value), 1));this.renderDropdown();});monthWrapper.appendChild(monthSelect);title.appendChild(monthWrapper);nav.appendChild(title);const nextBtn = createElement('button', `${PREFIX}__nav-button`);nextBtn.type = 'button';nextBtn.innerHTML = '<i class="icon-expand-right"></i>';if (isNextDisabled) nextBtn.disabled = true;nextBtn.addEventListener('click', () => {if (!isNextDisabled) {onViewDateChange(new Date(year, month + 1, 1));this.renderDropdown();}});nav.appendChild(nextBtn);return nav;}renderCalendarGrid(viewDate) {const grid = createElement('div', `${PREFIX}__calendar-grid`);const year = viewDate.getFullYear();const month = viewDate.getMonth();const today = new Date();const headerRow = createElement('div', `${PREFIX}__calendar-row`);this.texts.weekDays.forEach((day) => {const cell = createElement('div', `${PREFIX}__calendar-cell ${PREFIX}__calendar-cell--header`, day);headerRow.appendChild(cell);});grid.appendChild(headerRow);const daysInMonth = getDaysInMonth(year, month);const firstDay = getFirstDayOfMonth(year, month);const prevMonthDays = getDaysInMonth(year, month - 1);let days = [];for (let i = firstDay - 1; i >= 0; i--) {const day = prevMonthDays - i;const date = new Date(year, month - 1, day);days.push({ day, date, isOther: true });}for (let day = 1; day <= daysInMonth; day++) {const date = new Date(year, month, day);days.push({ day, date, isOther: false });}const totalCells = Math.ceil((firstDay + daysInMonth) / 7) * 7;const remainingDays = totalCells - (firstDay + daysInMonth);for (let day = 1; day <= remainingDays; day++) {const date = new Date(year, month + 1, day);days.push({ day, date, isOther: true });}for (let i = 0; i < days.length; i += 7) {const row = createElement('div', `${PREFIX}__calendar-row`);for (let j = 0; j < 7 && i + j < days.length; j++) {const { day, date, isOther } = days[i + j];const cell = this.createDayCell(day, date, isOther, today);row.appendChild(cell);}grid.appendChild(row);}return grid;}createDayCell(day, date, isOther, today) {const cell = createElement('button', `${PREFIX}__calendar-cell`);cell.type = 'button';cell.textContent = day;const isDisabled = this.checkDateDisabled(date);if (isOther) cell.classList.add(`${PREFIX}__calendar-cell--other`);if (isDisabled) {cell.classList.add(`${PREFIX}__calendar-cell--disabled`);cell.disabled = true;}const isToday = isSameDay(date, today);const isSelected = this.mode === 'instant' && isSameDay(date, this.tempValue.date);const isRangeStart = this.mode === 'period' && isSameDay(date, this.tempValue.date);const isRangeEnd = this.mode === 'period' && isSameDay(date, this.tempValue.endDate);const isInRangeDay =this.mode === 'period' &&this.tempValue.date &&this.tempValue.endDate &&isInRangeExclusive(date, this.tempValue.date, this.tempValue.endDate);if (isToday && !isSelected && !isRangeStart && !isRangeEnd) {cell.classList.add(`${PREFIX}__calendar-cell--today`);}if (isSelected) cell.classList.add(`${PREFIX}__calendar-cell--selected`);if (isRangeStart) cell.classList.add(`${PREFIX}__calendar-cell--range-start`);if (isRangeEnd) cell.classList.add(`${PREFIX}__calendar-cell--range-end`);if (isInRangeDay) cell.classList.add(`${PREFIX}__calendar-cell--in-range`);if (!isDisabled) {cell.addEventListener('click', () => this.handleDateSelect(date));}return cell;}checkDateDisabled(date) {if (isDateDisabled(date, this.disable, this.enable)) return true;if (isBeforeMinDate(date, this.minDate)) return true;if (isAfterMaxDate(date, this.maxDate)) return true;return false;}renderActions() {const actions = createElement('div', `${PREFIX}__actions`);const periodText = createElement('span', `${PREFIX}__period-text`);if (this.mode === 'period' && this.tempValue.date) {periodText.textContent = this.formatPeriodText();}actions.appendChild(periodText);const buttons = createElement('div', `${PREFIX}__action-buttons`);const resetBtn = createElement('button', `${PREFIX}__action-button ${PREFIX}__action-button--reset`);resetBtn.type = 'button';resetBtn.innerHTML = `<i class="icon-refresh"></i>${this.texts.reset}`;resetBtn.addEventListener('click', () => this.handleReset());buttons.appendChild(resetBtn);const applyBtn = createElement('button', `${PREFIX}__action-button ${PREFIX}__action-button--apply`);applyBtn.type = 'button';applyBtn.textContent = this.texts.apply;applyBtn.addEventListener('click', () => this.handleApply());buttons.appendChild(applyBtn);actions.appendChild(buttons);this.dropdownEl.appendChild(actions);}formatPeriodText() {if (!this.tempValue.date) return '';const formatKoreanDateTime = (date, time) => {const year = date.getFullYear();const month = date.getMonth() + 1;const day = date.getDate();let dateStr = `${year}년 ${month}월 ${day}일`;if (this.type === 'datetime' && time) {const hours = String(time.hour).padStart(2, '0');const minutes = String(time.minute).padStart(2, '0');dateStr += ` ${hours}:${minutes}`;}return dateStr;};const startText = formatKoreanDateTime(this.tempValue.date, this.tempValue.time);if (this.tempValue.endDate) {const endText = formatKoreanDateTime(this.tempValue.endDate, this.tempValue.endTime);return `${startText} ~ ${endText}`;}return startText;}bindEvents() {this.inputEl.addEventListener('click', (e) => {if (this.disabled) return;const target = e.target;if (target.dataset.part === 'date' || target.dataset.part === 'endDate') {this.toggleDropdown(target.dataset.part);}});this.inputContentEl.addEventListener('change', (e) => {if (e.target.tagName !== 'SELECT') return;const part = e.target.dataset.part;const value = parseInt(e.target.value);this.handleTimeChange(part, value);});document.addEventListener('mousedown', (e) => {if (!this.container.contains(e.target) && this.isOpen) {this.close();}});}toggleDropdown(part) {if (this.selectingPart === part && this.isOpen) {this.close();} else {this.selectingPart = part;this.open();}}open() {this.isOpen = true;this.inputEl.classList.add(`${PREFIX}__input--active`);this.dropdownEl.style.display = 'flex';this.renderDropdown();}close() {this.isOpen = false;this.selectingPart = null;this.inputEl.classList.remove(`${PREFIX}__input--active`);this.dropdownEl.style.display = 'none';}handleViewDateChange(date) {this.viewDate = date;}handleEndViewDateChange(date) {this.endViewDate = date;}handleDateSelect(date) {const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());if (this.mode === 'instant') {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { ...this.tempValue, date: newDate, time: adjustedTime };if (!this.showActions) {this.value = { ...this.tempValue };this.emitChange();}this.close();this.renderInputContent();return;}const existingStartDate = this.tempValue.date;const existingEndDate = this.tempValue.endDate;if (!existingStartDate) {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { ...this.tempValue, date: newDate, time: adjustedTime };} else if (!existingEndDate) {const dateOnly = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate());const startDateOnly = new Date(existingStartDate.getFullYear(),existingStartDate.getMonth(),existingStartDate.getDate());if (dateOnly < startDateOnly) {const adjustedStartTime = this.adjustTimeForDate(newDate, this.tempValue.time);const adjustedEndTime = this.adjustTimeForDate(existingStartDate, this.tempValue.time);this.tempValue = {date: newDate,time: adjustedStartTime,endDate: existingStartDate,endTime: adjustedEndTime,};} else {const adjustedEndTime = this.adjustTimeForDate(newDate, this.tempValue.endTime);this.tempValue = { ...this.tempValue, endDate: newDate, endTime: adjustedEndTime };}} else {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { date: newDate, time: adjustedTime, endDate: undefined, endTime: undefined };}this.renderDropdown();this.renderInputContent();}adjustTimeForDate(date, time) {if (!time) return time;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;let adjustedHour = time.hour;let adjustedMinute = time.minute;if (minLimit?.time && isSameDay(date, minLimit.date)) {if (adjustedHour < minLimit.time.hour) {adjustedHour = minLimit.time.hour;adjustedMinute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;} else if (adjustedHour === minLimit.time.hour && adjustedMinute < minLimit.time.minute) {adjustedMinute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (maxLimit?.time && isSameDay(date, maxLimit.date)) {if (adjustedHour > maxLimit.time.hour) {adjustedHour = maxLimit.time.hour;adjustedMinute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;} else if (adjustedHour === maxLimit.time.hour && adjustedMinute > maxLimit.time.minute) {adjustedMinute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (adjustedHour !== time.hour || adjustedMinute !== time.minute) {return { hour: adjustedHour, minute: adjustedMinute };}return time;}handleTimeChange(part, value) {const isEnd = part === 'endHour' || part === 'endMinute';const isHour = part === 'hour' || part === 'endHour';const currentTime = isEnd ? this.tempValue.endTime : this.tempValue.time;let newTime = { hour: currentTime?.hour ?? 0, minute: currentTime?.minute ?? 0 };if (isHour) {newTime.hour = value;const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;if (currentDate) {const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date) && value === minLimit.time.hour) {if (newTime.minute < minLimit.time.minute) {newTime.minute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date) && value === maxLimit.time.hour) {if (newTime.minute > maxLimit.time.minute) {newTime.minute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;}}}} else {newTime.minute = value;}if (isEnd) {this.tempValue = { ...this.tempValue, endTime: newTime };} else {this.tempValue = { ...this.tempValue, time: newTime };}if (!this.showActions) {this.value = { ...this.tempValue };this.emitChange();}this.renderInputContent();}handleReset() {this.tempValue = {};this.close();this.renderInputContent();}handleApply() {this.value = { ...this.tempValue };this.emitChange();this.close();this.renderInputContent();}emitChange() {if (this.onChange) {this.onChange(this.value);}}getValue() {return { ...this.value };}setValue(value) {this.value = value || {};this.tempValue = { ...this.value };if (value?.date) {this.viewDate = new Date(value.date);}if (value?.endDate) {this.endViewDate = new Date(value.endDate.getFullYear(), value.endDate.getMonth() + 1, 1);}this.renderInputContent();}clear() {this.value = {};this.tempValue = {};this.renderInputContent();this.emitChange();}enable() {this.disabled = false;this.inputEl.classList.remove(`${PREFIX}__input--disabled`);this.renderInputContent();}disable() {this.disabled = true;this.inputEl.classList.add(`${PREFIX}__input--disabled`);this.close();this.renderInputContent();}destroy() {this.container.innerHTML = '';}}return PodoDatePicker;});
1
+ /*! Podo UI DatePicker v0.8.2 | MIT License | https://podoui.com */
2
+ (function (global, factory) {typeof exports === 'object' && typeof module !== 'undefined'? (module.exports = factory()): typeof define === 'function' && define.amd? define(factory): ((global = typeof globalThis !== 'undefined' ? globalThis : global || self),(global.PodoDatePicker = factory()));})(this, function () {'use strict';const PREFIX = 'podo-datepicker';const DEFAULT_TEXTS = {weekDays: ['일', '월', '화', '수', '목', '금', '토'],months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],yearSuffix: '년',reset: '초기화',apply: '적용',};function resolveCalendarInitial(initial, fallback) {if (!initial) return fallback;if (initial instanceof Date) return initial;const now = new Date();switch (initial) {case 'now':return new Date(now.getFullYear(), now.getMonth(), 1);case 'prevMonth':return new Date(now.getFullYear(), now.getMonth() - 1, 1);case 'nextMonth':return new Date(now.getFullYear(), now.getMonth() + 1, 1);default:return fallback;}}function formatWithPattern(date, time, pattern) {if (!date && !time) return '';let result = pattern;if (date) {result = result.replace(/y/g, String(date.getFullYear()));result = result.replace(/m/g, String(date.getMonth() + 1).padStart(2, '0'));result = result.replace(/d/g, String(date.getDate()).padStart(2, '0'));}if (time) {result = result.replace(/h/g, String(time.hour).padStart(2, '0'));result = result.replace(/i/g, String(time.minute).padStart(2, '0'));}return result;}function getDateOnlyFormat(format) {if (!format) return undefined;return format.replace(/\s*h[:\s]*i[분]?/g, '').replace(/\s*h시\s*i분/g, '').trim();}function formatDate(date) {const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0');const day = String(date.getDate()).padStart(2, '0');return `${year} - ${month} - ${day}`;}function formatTime(hour, minute) {return `${String(hour).padStart(2, '0')} : ${String(minute).padStart(2, '0')}`;}function isSameDay(date1, date2) {if (!date1 || !date2) return false;return (date1.getFullYear() === date2.getFullYear() &&date1.getMonth() === date2.getMonth() &&date1.getDate() === date2.getDate());}function isInRange(date, start, end) {const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const s = new Date(start.getFullYear(), start.getMonth(), start.getDate());const e = new Date(end.getFullYear(), end.getMonth(), end.getDate());return d >= s && d <= e;}function isInRangeExclusive(date, start, end) {const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const s = new Date(start.getFullYear(), start.getMonth(), start.getDate());const e = new Date(end.getFullYear(), end.getMonth(), end.getDate());return d > s && d < e;}function getDaysInMonth(year, month) {return new Date(year, month + 1, 0).getDate();}function getFirstDayOfMonth(year, month) {return new Date(year, month, 1).getDay();}function isDateRange(condition) {return typeof condition === 'object' && condition !== null && 'from' in condition && 'to' in condition;}function matchesCondition(date, condition) {if (typeof condition === 'function') {return condition(date);}if (isDateRange(condition)) {return isInRange(date, condition.from, condition.to);}return isSameDay(date, condition);}function isDateDisabled(date, disable, enable) {if (enable && enable.length > 0) {const isEnabled = enable.some((condition) => matchesCondition(date, condition));return !isEnabled;}if (disable && disable.length > 0) {return disable.some((condition) => matchesCondition(date, condition));}return false;}function isDateTimeLimit(value) {return typeof value === 'object' && value !== null && 'date' in value && !(value instanceof Date);}function extractDateTimeLimit(limit) {if (isDateTimeLimit(limit)) {return { date: limit.date, time: limit.time };}return { date: limit };}function isBeforeMinDate(date, minDate) {if (!minDate) return false;const { date: minDateValue } = extractDateTimeLimit(minDate);const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const m = new Date(minDateValue.getFullYear(), minDateValue.getMonth(), minDateValue.getDate());return d < m;}function isAfterMaxDate(date, maxDate) {if (!maxDate) return false;const { date: maxDateValue } = extractDateTimeLimit(maxDate);const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());const m = new Date(maxDateValue.getFullYear(), maxDateValue.getMonth(), maxDateValue.getDate());return d > m;}function createElement(tag, className, content) {const el = document.createElement(tag);if (className) el.className = className;if (content !== undefined) {if (typeof content === 'string' || typeof content === 'number') {el.textContent = content;} else if (content instanceof HTMLElement) {el.appendChild(content);}}return el;}class PodoDatePicker {constructor(container, options = {}) {this.container =typeof container === 'string' ? document.querySelector(container) : container;if (!this.container) {throw new Error('PodoDatePicker: Container element not found');}this.mode = options.mode || 'instant';this.type = options.type || 'date';this.value = options.value || {};this.tempValue = { ...this.value };this.onChange = options.onChange;this.placeholder = options.placeholder;this.disabled = options.disabled || false;this.showActions = options.showActions ?? this.mode === 'period';this.align = options.align || 'left';this.disable = options.disable || [];this.enable = options.enable || [];this.minDate = options.minDate;this.maxDate = options.maxDate;this.minuteStep = options.minuteStep || 1;this.texts = { ...DEFAULT_TEXTS, ...options.texts };this.format = options.format;this.initialCalendar = options.initialCalendar || {};this.isOpen = false;this.selectingPart = null;if (this.value.date) {this.viewDate = new Date(this.value.date);} else if (this.initialCalendar.start) {this.viewDate = resolveCalendarInitial(this.initialCalendar.start, new Date());} else {this.viewDate = new Date();}if (this.value.endDate) {this.endViewDate = new Date(this.value.endDate.getFullYear(),this.value.endDate.getMonth() + 1,1);} else if (this.initialCalendar.end) {this.endViewDate = resolveCalendarInitial(this.initialCalendar.end, new Date());} else {this.endViewDate = new Date(this.viewDate.getFullYear(),this.viewDate.getMonth() + 1,1);}this.render();this.bindEvents();}render() {this.container.innerHTML = '';this.container.className = PREFIX;this.inputEl = createElement('div', `${PREFIX}__input`);if (this.disabled) this.inputEl.classList.add(`${PREFIX}__input--disabled`);this.inputContentEl = createElement('div', `${PREFIX}__input-content`);this.renderInputContent();this.inputEl.appendChild(this.inputContentEl);const iconClass = this.type === 'time' ? 'icon-time' : 'icon-calendar';this.iconEl = createElement('i', `${PREFIX}__icon ${iconClass}`);this.inputEl.appendChild(this.iconEl);this.container.appendChild(this.inputEl);this.dropdownEl = createElement('div',`${PREFIX}__dropdown ${this.align === 'right' ? `${PREFIX}__dropdown--right` : ''}`);this.dropdownEl.style.display = 'none';this.container.appendChild(this.dropdownEl);}renderInputContent() {this.inputContentEl.innerHTML = '';const displayValue = this.showActions ? this.tempValue : this.value;if (this.type === 'date') {this.renderDateInput(displayValue);} else if (this.type === 'time') {this.renderTimeInput(displayValue);} else {this.renderDateTimeInput(displayValue);}}renderDateInput(displayValue) {this.startDateBtn = this.createDateButton(displayValue.date, 'date');this.inputContentEl.appendChild(this.startDateBtn);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);this.endDateBtn = this.createDateButton(displayValue.endDate, 'endDate');this.inputContentEl.appendChild(this.endDateBtn);}}renderTimeInput(displayValue) {const startTimeSection = this.createTimeSection(displayValue.time, 'hour', 'minute');this.inputContentEl.appendChild(startTimeSection);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);const endTimeSection = this.createTimeSection(displayValue.endTime, 'endHour', 'endMinute');this.inputContentEl.appendChild(endTimeSection);}}renderDateTimeInput(displayValue) {this.startDateBtn = this.createDateButton(displayValue.date, 'date');this.inputContentEl.appendChild(this.startDateBtn);const startTimeSection = this.createTimeSection(displayValue.time, 'hour', 'minute');this.inputContentEl.appendChild(startTimeSection);if (this.mode === 'period') {const sep = createElement('span', `${PREFIX}__separator`, '~');this.inputContentEl.appendChild(sep);this.endDateBtn = this.createDateButton(displayValue.endDate, 'endDate');this.inputContentEl.appendChild(this.endDateBtn);const endTimeSection = this.createTimeSection(displayValue.endTime, 'endHour', 'endMinute');this.inputContentEl.appendChild(endTimeSection);}}createDateButton(date, part) {const btn = createElement('button', `${PREFIX}__part`);btn.type = 'button';const dateFormat = getDateOnlyFormat(this.format);if (!date) {btn.classList.add(`${PREFIX}__part--placeholder`);const placeholderText = dateFormat? dateFormat.replace(/y/g, 'YYYY').replace(/m/g, 'MM').replace(/d/g, 'DD'): 'YYYY - MM - DD';btn.textContent = placeholderText;} else {const displayText = dateFormat? formatWithPattern(date, null, dateFormat): formatDate(date);btn.textContent = displayText;}btn.dataset.part = part;return btn;}createTimeSection(time, hourPart, minutePart) {const section = createElement('div', `${PREFIX}__time-section`);const hourSelect = this.createHourSelect(time, hourPart);section.appendChild(hourSelect);const sep = createElement('span', `${PREFIX}__time-separator`, ':');section.appendChild(sep);const minuteSelect = this.createMinuteSelect(time, minutePart);section.appendChild(minuteSelect);return section;}createHourSelect(time, part) {const select = createElement('select', `${PREFIX}__time-select`);if (!time) select.classList.add(`${PREFIX}__time-select--placeholder`);if (this.disabled) select.disabled = true;const isEnd = part === 'endHour';const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;for (let h = 0; h < 24; h++) {const opt = createElement('option', null, String(h).padStart(2, '0'));opt.value = h;if (this.isHourDisabled(h, currentDate)) {opt.disabled = true;}select.appendChild(opt);}select.value = time?.hour ?? 0;select.dataset.part = part;return select;}createMinuteSelect(time, part) {const select = createElement('select', `${PREFIX}__time-select`);if (!time) select.classList.add(`${PREFIX}__time-select--placeholder`);if (this.disabled) select.disabled = true;const isEnd = part === 'endMinute';const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;const currentTime = isEnd ? this.tempValue.endTime : this.tempValue.time;for (let m = 0; m < 60; m += this.minuteStep) {const opt = createElement('option', null, String(m).padStart(2, '0'));opt.value = m;if (this.isMinuteDisabled(m, currentDate, currentTime)) {opt.disabled = true;}select.appendChild(opt);}let minute = time?.minute ?? 0;if (minute % this.minuteStep !== 0) {minute = Math.floor(minute / this.minuteStep) * this.minuteStep;}select.value = minute;select.dataset.part = part;return select;}isHourDisabled(h, currentDate) {if (!currentDate) return false;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date)) {if (h < minLimit.time.hour) return true;}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date)) {if (h > maxLimit.time.hour) return true;}return false;}isMinuteDisabled(m, currentDate, currentTime) {if (!currentDate || !currentTime) return false;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date) && currentTime.hour === minLimit.time.hour) {if (m < minLimit.time.minute) return true;}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date) && currentTime.hour === maxLimit.time.hour) {if (m > maxLimit.time.minute) return true;}return false;}renderDropdown() {this.dropdownEl.innerHTML = '';if (this.mode === 'period') {this.renderPeriodCalendars();} else {this.renderCalendar(this.viewDate, (date) => this.handleViewDateChange(date));}if (this.showActions) {this.renderActions();}}renderCalendar(viewDate, onViewDateChange, opts = {}) {const calendar = createElement('div', `${PREFIX}__calendar`);const nav = this.renderCalendarNav(viewDate, onViewDateChange, opts);calendar.appendChild(nav);const grid = this.renderCalendarGrid(viewDate);calendar.appendChild(grid);this.dropdownEl.appendChild(calendar);return calendar;}renderPeriodCalendars() {const wrapper = createElement('div', `${PREFIX}__period-calendars`);const leftCal = createElement('div', `${PREFIX}__period-calendar-left`);const leftCalendar = this.createCalendarElement(this.viewDate,(date) => this.handleViewDateChange(date),{ maxViewDate: this.endViewDate });leftCal.appendChild(leftCalendar);wrapper.appendChild(leftCal);const rightCal = createElement('div', `${PREFIX}__period-calendar-right`);const rightCalendar = this.createCalendarElement(this.endViewDate,(date) => this.handleEndViewDateChange(date),{ minViewDate: this.viewDate });rightCal.appendChild(rightCalendar);wrapper.appendChild(rightCal);this.dropdownEl.appendChild(wrapper);}createCalendarElement(viewDate, onViewDateChange, opts = {}) {const calendar = createElement('div', `${PREFIX}__calendar`);const nav = this.renderCalendarNav(viewDate, onViewDateChange, opts);calendar.appendChild(nav);const grid = this.renderCalendarGrid(viewDate);calendar.appendChild(grid);return calendar;}renderCalendarNav(viewDate, onViewDateChange, opts = {}) {const nav = createElement('div', `${PREFIX}__calendar-nav`);const year = viewDate.getFullYear();const month = viewDate.getMonth();const minViewDate = opts.minViewDate;const maxViewDate = opts.maxViewDate;const minYear = minViewDate?.getFullYear();const minMonth = minViewDate?.getMonth();const maxYear = maxViewDate?.getFullYear();const maxMonth = maxViewDate?.getMonth();const isPrevDisabled = minViewDate? year < minYear || (year === minYear && month <= minMonth): false;const isNextDisabled = maxViewDate? year > maxYear || (year === maxYear && month >= maxMonth): false;const prevBtn = createElement('button', `${PREFIX}__nav-button`);prevBtn.type = 'button';prevBtn.innerHTML = '<i class="icon-expand-left"></i>';if (isPrevDisabled) prevBtn.disabled = true;prevBtn.addEventListener('click', () => {if (!isPrevDisabled) {onViewDateChange(new Date(year, month - 1, 1));this.renderDropdown();}});nav.appendChild(prevBtn);const title = createElement('div', `${PREFIX}__nav-title`);const yearWrapper = createElement('div', `${PREFIX}__nav-select-wrapper`);const yearSelect = createElement('select', `${PREFIX}__nav-select`);const currentYear = new Date().getFullYear();for (let y = currentYear - 10; y <= currentYear + 10; y++) {if (minYear !== undefined && y < minYear) continue;if (maxYear !== undefined && y > maxYear) continue;const opt = createElement('option', null, `${y}${this.texts.yearSuffix}`);opt.value = y;yearSelect.appendChild(opt);}yearSelect.value = year;yearSelect.addEventListener('change', (e) => {onViewDateChange(new Date(parseInt(e.target.value), month, 1));this.renderDropdown();});yearWrapper.appendChild(yearSelect);title.appendChild(yearWrapper);const monthWrapper = createElement('div', `${PREFIX}__nav-select-wrapper`);const monthSelect = createElement('select', `${PREFIX}__nav-select`);for (let m = 0; m < 12; m++) {if (minYear !== undefined && minMonth !== undefined && year === minYear && m < minMonth) continue;if (maxYear !== undefined && maxMonth !== undefined && year === maxYear && m > maxMonth) continue;const opt = createElement('option', null, this.texts.months[m]);opt.value = m;monthSelect.appendChild(opt);}monthSelect.value = month;monthSelect.addEventListener('change', (e) => {onViewDateChange(new Date(year, parseInt(e.target.value), 1));this.renderDropdown();});monthWrapper.appendChild(monthSelect);title.appendChild(monthWrapper);nav.appendChild(title);const nextBtn = createElement('button', `${PREFIX}__nav-button`);nextBtn.type = 'button';nextBtn.innerHTML = '<i class="icon-expand-right"></i>';if (isNextDisabled) nextBtn.disabled = true;nextBtn.addEventListener('click', () => {if (!isNextDisabled) {onViewDateChange(new Date(year, month + 1, 1));this.renderDropdown();}});nav.appendChild(nextBtn);return nav;}renderCalendarGrid(viewDate) {const grid = createElement('div', `${PREFIX}__calendar-grid`);const year = viewDate.getFullYear();const month = viewDate.getMonth();const today = new Date();const headerRow = createElement('div', `${PREFIX}__calendar-row`);this.texts.weekDays.forEach((day) => {const cell = createElement('div', `${PREFIX}__calendar-cell ${PREFIX}__calendar-cell--header`, day);headerRow.appendChild(cell);});grid.appendChild(headerRow);const daysInMonth = getDaysInMonth(year, month);const firstDay = getFirstDayOfMonth(year, month);const prevMonthDays = getDaysInMonth(year, month - 1);let days = [];for (let i = firstDay - 1; i >= 0; i--) {const day = prevMonthDays - i;const date = new Date(year, month - 1, day);days.push({ day, date, isOther: true });}for (let day = 1; day <= daysInMonth; day++) {const date = new Date(year, month, day);days.push({ day, date, isOther: false });}const totalCells = Math.ceil((firstDay + daysInMonth) / 7) * 7;const remainingDays = totalCells - (firstDay + daysInMonth);for (let day = 1; day <= remainingDays; day++) {const date = new Date(year, month + 1, day);days.push({ day, date, isOther: true });}for (let i = 0; i < days.length; i += 7) {const row = createElement('div', `${PREFIX}__calendar-row`);for (let j = 0; j < 7 && i + j < days.length; j++) {const { day, date, isOther } = days[i + j];const cell = this.createDayCell(day, date, isOther, today);row.appendChild(cell);}grid.appendChild(row);}return grid;}createDayCell(day, date, isOther, today) {const cell = createElement('button', `${PREFIX}__calendar-cell`);cell.type = 'button';cell.textContent = day;const isDisabled = this.checkDateDisabled(date);if (isOther) cell.classList.add(`${PREFIX}__calendar-cell--other`);if (isDisabled) {cell.classList.add(`${PREFIX}__calendar-cell--disabled`);cell.disabled = true;}const isToday = isSameDay(date, today);const isSelected = this.mode === 'instant' && isSameDay(date, this.tempValue.date);const isRangeStart = this.mode === 'period' && isSameDay(date, this.tempValue.date);const isRangeEnd = this.mode === 'period' && isSameDay(date, this.tempValue.endDate);const isInRangeDay =this.mode === 'period' &&this.tempValue.date &&this.tempValue.endDate &&isInRangeExclusive(date, this.tempValue.date, this.tempValue.endDate);if (isToday && !isSelected && !isRangeStart && !isRangeEnd) {cell.classList.add(`${PREFIX}__calendar-cell--today`);}if (isSelected) cell.classList.add(`${PREFIX}__calendar-cell--selected`);if (isRangeStart) cell.classList.add(`${PREFIX}__calendar-cell--range-start`);if (isRangeEnd) cell.classList.add(`${PREFIX}__calendar-cell--range-end`);if (isInRangeDay) cell.classList.add(`${PREFIX}__calendar-cell--in-range`);if (!isDisabled) {cell.addEventListener('click', () => this.handleDateSelect(date));}return cell;}checkDateDisabled(date) {if (isDateDisabled(date, this.disable, this.enable)) return true;if (isBeforeMinDate(date, this.minDate)) return true;if (isAfterMaxDate(date, this.maxDate)) return true;return false;}renderActions() {const actions = createElement('div', `${PREFIX}__actions`);const periodText = createElement('span', `${PREFIX}__period-text`);if (this.mode === 'period' && this.tempValue.date) {periodText.textContent = this.formatPeriodText();}actions.appendChild(periodText);const buttons = createElement('div', `${PREFIX}__action-buttons`);const resetBtn = createElement('button', `${PREFIX}__action-button ${PREFIX}__action-button--reset`);resetBtn.type = 'button';resetBtn.innerHTML = `<i class="icon-refresh"></i>${this.texts.reset}`;resetBtn.addEventListener('click', () => this.handleReset());buttons.appendChild(resetBtn);const applyBtn = createElement('button', `${PREFIX}__action-button ${PREFIX}__action-button--apply`);applyBtn.type = 'button';applyBtn.textContent = this.texts.apply;applyBtn.addEventListener('click', () => this.handleApply());buttons.appendChild(applyBtn);actions.appendChild(buttons);this.dropdownEl.appendChild(actions);}formatPeriodText() {if (!this.tempValue.date) return '';if (this.format) {const startText = formatWithPattern(this.tempValue.date, this.tempValue.time, this.format);if (this.tempValue.endDate) {const endText = formatWithPattern(this.tempValue.endDate, this.tempValue.endTime, this.format);return `${startText} ~ ${endText}`;}return startText;}const formatKoreanDateTime = (date, time) => {const year = date.getFullYear();const month = date.getMonth() + 1;const day = date.getDate();let dateStr = `${year}년 ${month}월 ${day}일`;if (this.type === 'datetime' && time) {const hours = String(time.hour).padStart(2, '0');const minutes = String(time.minute).padStart(2, '0');dateStr += ` ${hours}:${minutes}`;}return dateStr;};const startText = formatKoreanDateTime(this.tempValue.date, this.tempValue.time);if (this.tempValue.endDate) {const endText = formatKoreanDateTime(this.tempValue.endDate, this.tempValue.endTime);return `${startText} ~ ${endText}`;}return startText;}bindEvents() {this.inputEl.addEventListener('click', (e) => {if (this.disabled) return;const target = e.target;if (target.dataset.part === 'date' || target.dataset.part === 'endDate') {this.toggleDropdown(target.dataset.part);}});this.inputContentEl.addEventListener('change', (e) => {if (e.target.tagName !== 'SELECT') return;const part = e.target.dataset.part;const value = parseInt(e.target.value);this.handleTimeChange(part, value);});document.addEventListener('mousedown', (e) => {if (!this.container.contains(e.target) && this.isOpen) {this.close();}});}toggleDropdown(part) {if (this.selectingPart === part && this.isOpen) {this.close();} else {this.selectingPart = part;this.open();}}open() {this.isOpen = true;this.inputEl.classList.add(`${PREFIX}__input--active`);this.dropdownEl.style.display = 'flex';this.renderDropdown();}close() {this.isOpen = false;this.selectingPart = null;this.inputEl.classList.remove(`${PREFIX}__input--active`);this.dropdownEl.style.display = 'none';}handleViewDateChange(date) {this.viewDate = date;}handleEndViewDateChange(date) {this.endViewDate = date;}handleDateSelect(date) {const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());if (this.mode === 'instant') {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { ...this.tempValue, date: newDate, time: adjustedTime };if (!this.showActions) {this.value = { ...this.tempValue };this.emitChange();}this.close();this.renderInputContent();return;}const existingStartDate = this.tempValue.date;const existingEndDate = this.tempValue.endDate;if (!existingStartDate) {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { ...this.tempValue, date: newDate, time: adjustedTime };} else if (!existingEndDate) {const dateOnly = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate());const startDateOnly = new Date(existingStartDate.getFullYear(),existingStartDate.getMonth(),existingStartDate.getDate());if (dateOnly < startDateOnly) {const adjustedStartTime = this.adjustTimeForDate(newDate, this.tempValue.time);const adjustedEndTime = this.adjustTimeForDate(existingStartDate, this.tempValue.time);this.tempValue = {date: newDate,time: adjustedStartTime,endDate: existingStartDate,endTime: adjustedEndTime,};} else {const adjustedEndTime = this.adjustTimeForDate(newDate, this.tempValue.endTime);this.tempValue = { ...this.tempValue, endDate: newDate, endTime: adjustedEndTime };}} else {const adjustedTime = this.adjustTimeForDate(newDate, this.tempValue.time);this.tempValue = { date: newDate, time: adjustedTime, endDate: undefined, endTime: undefined };}this.renderDropdown();this.renderInputContent();}adjustTimeForDate(date, time) {if (!time) return time;const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;let adjustedHour = time.hour;let adjustedMinute = time.minute;if (minLimit?.time && isSameDay(date, minLimit.date)) {if (adjustedHour < minLimit.time.hour) {adjustedHour = minLimit.time.hour;adjustedMinute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;} else if (adjustedHour === minLimit.time.hour && adjustedMinute < minLimit.time.minute) {adjustedMinute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (maxLimit?.time && isSameDay(date, maxLimit.date)) {if (adjustedHour > maxLimit.time.hour) {adjustedHour = maxLimit.time.hour;adjustedMinute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;} else if (adjustedHour === maxLimit.time.hour && adjustedMinute > maxLimit.time.minute) {adjustedMinute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (adjustedHour !== time.hour || adjustedMinute !== time.minute) {return { hour: adjustedHour, minute: adjustedMinute };}return time;}handleTimeChange(part, value) {const isEnd = part === 'endHour' || part === 'endMinute';const isHour = part === 'hour' || part === 'endHour';const currentTime = isEnd ? this.tempValue.endTime : this.tempValue.time;let newTime = { hour: currentTime?.hour ?? 0, minute: currentTime?.minute ?? 0 };if (isHour) {newTime.hour = value;const currentDate = isEnd ? this.tempValue.endDate : this.tempValue.date;if (currentDate) {const minLimit = this.minDate ? extractDateTimeLimit(this.minDate) : null;const maxLimit = this.maxDate ? extractDateTimeLimit(this.maxDate) : null;if (minLimit?.time && isSameDay(currentDate, minLimit.date) && value === minLimit.time.hour) {if (newTime.minute < minLimit.time.minute) {newTime.minute = Math.ceil(minLimit.time.minute / this.minuteStep) * this.minuteStep;}}if (maxLimit?.time && isSameDay(currentDate, maxLimit.date) && value === maxLimit.time.hour) {if (newTime.minute > maxLimit.time.minute) {newTime.minute = Math.floor(maxLimit.time.minute / this.minuteStep) * this.minuteStep;}}}} else {newTime.minute = value;}if (isEnd) {this.tempValue = { ...this.tempValue, endTime: newTime };} else {this.tempValue = { ...this.tempValue, time: newTime };}if (!this.showActions) {this.value = { ...this.tempValue };this.emitChange();}this.renderInputContent();}handleReset() {this.tempValue = {};this.close();this.renderInputContent();}handleApply() {this.value = { ...this.tempValue };this.emitChange();this.close();this.renderInputContent();}emitChange() {if (this.onChange) {this.onChange(this.value);}}getValue() {return { ...this.value };}setValue(value) {this.value = value || {};this.tempValue = { ...this.value };if (value?.date) {this.viewDate = new Date(value.date);}if (value?.endDate) {this.endViewDate = new Date(value.endDate.getFullYear(), value.endDate.getMonth() + 1, 1);}this.renderInputContent();}clear() {this.value = {};this.tempValue = {};this.renderInputContent();this.emitChange();}enable() {this.disabled = false;this.inputEl.classList.remove(`${PREFIX}__input--disabled`);this.renderInputContent();}disable() {this.disabled = true;this.inputEl.classList.add(`${PREFIX}__input--disabled`);this.close();this.renderInputContent();}destroy() {this.container.innerHTML = '';}}return PodoDatePicker;});
package/cdn/podo-ui.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Podo UI CSS v0.8.1
2
+ * Podo UI CSS v0.8.2
3
3
  * https://podoui.com
4
4
  * MIT License
5
5
  */