millas 0.2.12-beta → 0.2.12-beta-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.
Files changed (116) hide show
  1. package/package.json +3 -16
  2. package/src/admin/ActivityLog.js +153 -52
  3. package/src/admin/Admin.js +400 -167
  4. package/src/admin/AdminAuth.js +213 -98
  5. package/src/admin/FormGenerator.js +372 -0
  6. package/src/admin/HookRegistry.js +256 -0
  7. package/src/admin/QueryEngine.js +263 -0
  8. package/src/admin/ViewContext.js +309 -0
  9. package/src/admin/WidgetRegistry.js +406 -0
  10. package/src/admin/index.js +17 -0
  11. package/src/admin/resources/AdminResource.js +383 -97
  12. package/src/admin/static/admin.css +1341 -0
  13. package/src/admin/static/date-picker.css +157 -0
  14. package/src/admin/static/date-picker.js +316 -0
  15. package/src/admin/static/json-editor.css +649 -0
  16. package/src/admin/static/json-editor.js +1429 -0
  17. package/src/admin/static/ui.js +1044 -0
  18. package/src/admin/views/layouts/base.njk +65 -1013
  19. package/src/admin/views/pages/detail.njk +40 -16
  20. package/src/admin/views/pages/form.njk +47 -599
  21. package/src/admin/views/pages/list.njk +145 -62
  22. package/src/admin/views/partials/form-field.njk +53 -0
  23. package/src/admin/views/partials/form-footer.njk +28 -0
  24. package/src/admin/views/partials/form-readonly.njk +114 -0
  25. package/src/admin/views/partials/form-scripts.njk +476 -0
  26. package/src/admin/views/partials/form-widget.njk +296 -0
  27. package/src/admin/views/partials/json-dialog.njk +80 -0
  28. package/src/admin/views/partials/json-editor.njk +37 -0
  29. package/src/admin.zip +0 -0
  30. package/src/auth/Auth.js +31 -10
  31. package/src/auth/AuthController.js +3 -1
  32. package/src/auth/AuthUser.js +119 -0
  33. package/src/cli.js +4 -2
  34. package/src/commands/createsuperuser.js +254 -0
  35. package/src/commands/lang.js +589 -0
  36. package/src/commands/migrate.js +154 -81
  37. package/src/commands/serve.js +82 -110
  38. package/src/container/AppInitializer.js +215 -0
  39. package/src/container/Application.js +278 -253
  40. package/src/container/HttpServer.js +156 -0
  41. package/src/container/MillasApp.js +29 -279
  42. package/src/container/MillasConfig.js +192 -0
  43. package/src/core/admin.js +5 -0
  44. package/src/core/auth.js +9 -0
  45. package/src/core/db.js +9 -0
  46. package/src/core/foundation.js +59 -0
  47. package/src/core/http.js +11 -0
  48. package/src/core/lang.js +1 -0
  49. package/src/core/mail.js +6 -0
  50. package/src/core/queue.js +7 -0
  51. package/src/core/validation.js +29 -0
  52. package/src/facades/Admin.js +1 -1
  53. package/src/facades/Auth.js +22 -39
  54. package/src/facades/Cache.js +21 -10
  55. package/src/facades/Database.js +1 -1
  56. package/src/facades/Events.js +18 -17
  57. package/src/facades/Facade.js +197 -0
  58. package/src/facades/Http.js +42 -45
  59. package/src/facades/Log.js +25 -49
  60. package/src/facades/Mail.js +27 -32
  61. package/src/facades/Queue.js +22 -15
  62. package/src/facades/Storage.js +18 -10
  63. package/src/facades/Url.js +53 -0
  64. package/src/http/HttpClient.js +673 -0
  65. package/src/http/ResponseDispatcher.js +18 -111
  66. package/src/http/UrlGenerator.js +375 -0
  67. package/src/http/WelcomePage.js +273 -0
  68. package/src/http/adapters/ExpressAdapter.js +315 -0
  69. package/src/http/adapters/HttpAdapter.js +168 -0
  70. package/src/http/adapters/index.js +9 -0
  71. package/src/i18n/I18nServiceProvider.js +91 -0
  72. package/src/i18n/Translator.js +635 -0
  73. package/src/i18n/defaults.js +122 -0
  74. package/src/i18n/index.js +164 -0
  75. package/src/i18n/locales/en.js +55 -0
  76. package/src/i18n/locales/sw.js +48 -0
  77. package/src/index.js +5 -144
  78. package/src/logger/formatters/PrettyFormatter.js +103 -57
  79. package/src/logger/internal.js +2 -2
  80. package/src/logger/patchConsole.js +91 -81
  81. package/src/middleware/MiddlewareRegistry.js +62 -82
  82. package/src/migrations/system/0001_users.js +21 -0
  83. package/src/migrations/system/0002_admin_log.js +25 -0
  84. package/src/migrations/system/0003_sessions.js +23 -0
  85. package/src/orm/fields/index.js +210 -188
  86. package/src/orm/migration/DefaultValueParser.js +325 -0
  87. package/src/orm/migration/InteractiveResolver.js +191 -0
  88. package/src/orm/migration/Makemigrations.js +312 -0
  89. package/src/orm/migration/MigrationGraph.js +227 -0
  90. package/src/orm/migration/MigrationRunner.js +202 -108
  91. package/src/orm/migration/MigrationWriter.js +463 -0
  92. package/src/orm/migration/ModelInspector.js +412 -344
  93. package/src/orm/migration/ModelScanner.js +225 -0
  94. package/src/orm/migration/ProjectState.js +213 -0
  95. package/src/orm/migration/RenameDetector.js +175 -0
  96. package/src/orm/migration/SchemaBuilder.js +8 -81
  97. package/src/orm/migration/operations/base.js +57 -0
  98. package/src/orm/migration/operations/column.js +191 -0
  99. package/src/orm/migration/operations/fields.js +252 -0
  100. package/src/orm/migration/operations/index.js +55 -0
  101. package/src/orm/migration/operations/models.js +152 -0
  102. package/src/orm/migration/operations/registry.js +131 -0
  103. package/src/orm/migration/operations/special.js +51 -0
  104. package/src/orm/migration/utils.js +208 -0
  105. package/src/orm/model/Model.js +81 -13
  106. package/src/providers/AdminServiceProvider.js +66 -9
  107. package/src/providers/AuthServiceProvider.js +46 -7
  108. package/src/providers/CacheStorageServiceProvider.js +5 -3
  109. package/src/providers/DatabaseServiceProvider.js +3 -2
  110. package/src/providers/EventServiceProvider.js +2 -1
  111. package/src/providers/LogServiceProvider.js +7 -3
  112. package/src/providers/MailServiceProvider.js +4 -3
  113. package/src/providers/QueueServiceProvider.js +4 -3
  114. package/src/router/Router.js +119 -152
  115. package/src/scaffold/templates.js +83 -26
  116. package/src/facades/Validation.js +0 -69
@@ -0,0 +1,157 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ DATE PICKER
3
+ ═══════════════════════════════════════════════════════════════ */
4
+
5
+ /* Trigger input — looks like a normal form-control */
6
+ .dp-trigger { cursor: pointer; user-select: none; caret-color: transparent; }
7
+ .dp-trigger:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
8
+
9
+ /* Panel wrapper */
10
+ .dp-panel {
11
+ background: var(--surface);
12
+ border: 1px solid var(--border);
13
+ border-radius: var(--radius);
14
+ box-shadow: var(--shadow-lg);
15
+ width: 268px;
16
+ max-width: calc(100vw - 16px);
17
+ min-width: 240px;
18
+ overflow-y: visible !important; /* never scroll the calendar — flip above instead */
19
+ overflow-x: hidden;
20
+ font-size: 13px;
21
+ user-select: none;
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ /* Header: ‹ Month Year › */
26
+ .dp-header {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ padding: 10px 12px 8px;
31
+ border-bottom: 1px solid var(--border-soft);
32
+ }
33
+ .dp-nav-btn {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ width: 28px;
38
+ height: 28px;
39
+ border: 1px solid var(--border);
40
+ border-radius: var(--radius-sm);
41
+ background: none;
42
+ cursor: pointer;
43
+ color: var(--text-muted);
44
+ transition: background .1s, color .1s;
45
+ }
46
+ .dp-nav-btn:hover { background: var(--surface2); color: var(--text); }
47
+ .dp-month-label {
48
+ font-size: 13.5px;
49
+ font-weight: 600;
50
+ color: var(--text);
51
+ }
52
+
53
+ /* Weekday headers */
54
+ .dp-weekdays {
55
+ display: grid;
56
+ grid-template-columns: repeat(7, 1fr);
57
+ padding: 6px 10px 2px;
58
+ }
59
+ .dp-day-hdr {
60
+ text-align: center;
61
+ font-size: 11px;
62
+ font-weight: 600;
63
+ color: var(--text-xmuted);
64
+ text-transform: uppercase;
65
+ letter-spacing: .3px;
66
+ }
67
+
68
+ /* Date grid */
69
+ .dp-grid {
70
+ display: grid;
71
+ grid-template-columns: repeat(7, 1fr);
72
+ padding: 2px 10px 6px;
73
+ gap: 1px;
74
+ }
75
+ .dp-cell {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ height: 30px;
80
+ border-radius: var(--radius-sm);
81
+ border: none;
82
+ background: none;
83
+ font-size: 12.5px;
84
+ color: var(--text-soft);
85
+ cursor: pointer;
86
+ transition: background .1s, color .1s;
87
+ font-family: inherit;
88
+ }
89
+ .dp-cell:hover { background: var(--surface2); color: var(--text); }
90
+ .dp-muted { color: var(--text-xmuted); }
91
+ .dp-muted:hover { background: none; cursor: default; }
92
+ .dp-today { font-weight: 700; color: var(--primary); }
93
+ .dp-today::after { content: ''; display: block; width: 3px; height: 3px; border-radius: 99px; background: var(--primary); margin: 1px auto 0; }
94
+ .dp-selected { background: var(--primary) !important; color: #fff !important; font-weight: 600; }
95
+ .dp-selected:hover { background: var(--primary-h) !important; }
96
+
97
+ /* Shortcuts */
98
+ .dp-shortcuts {
99
+ display: flex;
100
+ gap: 4px;
101
+ padding: 6px 10px;
102
+ border-top: 1px solid var(--border-soft);
103
+ }
104
+ .dp-shortcut {
105
+ flex: 1;
106
+ padding: 5px 0;
107
+ border: 1px solid var(--border);
108
+ border-radius: var(--radius-sm);
109
+ background: none;
110
+ font-size: 11.5px;
111
+ font-weight: 500;
112
+ color: var(--text-muted);
113
+ cursor: pointer;
114
+ font-family: inherit;
115
+ transition: background .1s, color .1s, border-color .1s;
116
+ text-align: center;
117
+ }
118
+ .dp-shortcut:hover { background: var(--primary-soft); color: var(--primary); border-color: var(--primary-dim); }
119
+
120
+ /* Time row (datetime mode only) */
121
+ .dp-time-row {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 10px;
125
+ padding: 8px 12px;
126
+ border-top: 1px solid var(--border-soft);
127
+ }
128
+ .dp-time-label { font-size: 12px; font-weight: 500; color: var(--text-muted); flex-shrink: 0; }
129
+ .dp-time-input {
130
+ flex: 1;
131
+ border: 1px solid var(--border);
132
+ border-radius: var(--radius-sm);
133
+ padding: 5px 8px;
134
+ font-size: 13px;
135
+ font-family: 'DM Mono', monospace;
136
+ color: var(--text);
137
+ background: var(--surface2);
138
+ outline: none;
139
+ }
140
+ .dp-time-input:focus { border-color: var(--primary); background: var(--surface); box-shadow: 0 0 0 2px var(--primary-soft); }
141
+
142
+
143
+
144
+ /* dp-wrap: positions the calendar icon inside the trigger input */
145
+ .dp-wrap {
146
+ position: relative;
147
+ }
148
+ .dp-wrap .form-control { padding-right: 34px; }
149
+ .dp-icon {
150
+ position: absolute;
151
+ right: 10px;
152
+ top: 50%;
153
+ transform: translateY(-50%);
154
+ color: var(--text-muted);
155
+ pointer-events: none;
156
+ display: flex;
157
+ }
@@ -0,0 +1,316 @@
1
+ /**
2
+ * date-picker.js — Custom date/datetime picker for Millas Admin
3
+ *
4
+ * Django-style: month nav header, week labels, date grid, shortcuts.
5
+ * Portal-rendered via UI.Dropdown so it is never clipped by parent overflow.
6
+ * Works for both `date` and `datetime-local` fields.
7
+ *
8
+ * Usage (automatic — attaches to all .dp-trigger inputs on DOMContentLoaded):
9
+ * <input type="text" class="dp-trigger form-control"
10
+ * data-target="field-my_date"
11
+ * data-mode="date"> <!-- or "datetime" -->
12
+ *
13
+ * Exposed on window.DatePicker for programmatic use.
14
+ */
15
+ (function ($, root) {
16
+ 'use strict';
17
+
18
+ var DAYS_SHORT = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
19
+ var MONTHS = ['January','February','March','April','May','June',
20
+ 'July','August','September','October','November','December'];
21
+
22
+ // ── Shared calendar panel (one, reused) ───────────────────────────────────
23
+ var _panel = null; // DOM element
24
+ var _dd = null; // UI.Dropdown instance
25
+ var _target = null; // current hidden input el
26
+ var _trigger = null; // current trigger input el
27
+ var _mode = 'date'; // 'date' | 'datetime'
28
+ var _current = null; // Date being viewed (month/year nav)
29
+ var _selected = null; // currently selected Date
30
+
31
+ function _buildPanel() {
32
+ var el = document.createElement('div');
33
+ el.className = 'dp-panel';
34
+ el.innerHTML =
35
+ '<div class="dp-header">' +
36
+ '<button type="button" class="dp-nav-btn" id="dp-prev" title="Previous month">' +
37
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>' +
38
+ '</button>' +
39
+ '<span class="dp-month-label" id="dp-month-label"></span>' +
40
+ '<button type="button" class="dp-nav-btn" id="dp-next" title="Next month">' +
41
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>' +
42
+ '</button>' +
43
+ '</div>' +
44
+ '<div class="dp-weekdays" id="dp-weekdays"></div>' +
45
+ '<div class="dp-grid" id="dp-grid"></div>' +
46
+ '<div class="dp-shortcuts">' +
47
+ '<button type="button" class="dp-shortcut" data-offset="-1">Yesterday</button>' +
48
+ '<button type="button" class="dp-shortcut" data-offset="0">Today</button>' +
49
+ '<button type="button" class="dp-shortcut" data-offset="1">Tomorrow</button>' +
50
+ '</div>' +
51
+ '<div class="dp-time-row" id="dp-time-row" style="display:none">' +
52
+ '<label class="dp-time-label">Time</label>' +
53
+ '<input type="time" class="dp-time-input" id="dp-time-input" step="60">' +
54
+ '</div>' +
55
+ '';
56
+
57
+ // Weekday headers
58
+ var $days = el.querySelector('#dp-weekdays');
59
+ DAYS_SHORT.forEach(function (d) {
60
+ var s = document.createElement('span');
61
+ s.className = 'dp-day-hdr';
62
+ s.textContent = d;
63
+ $days.appendChild(s);
64
+ });
65
+
66
+ // Month nav
67
+ el.querySelector('#dp-prev').addEventListener('click', function () {
68
+ _current.setMonth(_current.getMonth() - 1);
69
+ _renderGrid();
70
+ });
71
+ el.querySelector('#dp-next').addEventListener('click', function () {
72
+ _current.setMonth(_current.getMonth() + 1);
73
+ _renderGrid();
74
+ });
75
+
76
+ // Shortcuts
77
+ el.querySelectorAll('.dp-shortcut').forEach(function (btn) {
78
+ btn.addEventListener('click', function () {
79
+ var offset = parseInt(btn.dataset.offset);
80
+ var d = new Date();
81
+ d.setDate(d.getDate() + offset);
82
+ _selected = d;
83
+ _current = new Date(d.getFullYear(), d.getMonth(), 1);
84
+ _renderGrid();
85
+ if (_mode === 'date') _apply();
86
+ });
87
+ });
88
+
89
+
90
+ return el;
91
+ }
92
+
93
+ function _renderGrid() {
94
+ var label = el('#dp-month-label');
95
+ if (label) label.textContent = MONTHS[_current.getMonth()] + ' ' + _current.getFullYear();
96
+
97
+ var grid = el('#dp-grid');
98
+ if (!grid) return;
99
+ grid.innerHTML = '';
100
+
101
+ var today = new Date();
102
+ today.setHours(0,0,0,0);
103
+
104
+ var year = _current.getFullYear();
105
+ var month = _current.getMonth();
106
+
107
+ // First day of month (0=Sun)
108
+ var firstDay = new Date(year, month, 1).getDay();
109
+ // Days in month
110
+ var daysInMonth = new Date(year, month + 1, 0).getDate();
111
+ // Days in prev month
112
+ var daysInPrev = new Date(year, month, 0).getDate();
113
+
114
+ // Previous month filler
115
+ for (var p = firstDay - 1; p >= 0; p--) {
116
+ grid.appendChild(_cell(daysInPrev - p, false, false, true));
117
+ }
118
+
119
+ // Current month days
120
+ for (var d = 1; d <= daysInMonth; d++) {
121
+ var date = new Date(year, month, d);
122
+ var isTod = date.getTime() === today.getTime();
123
+ var isSel = _selected
124
+ ? (date.getFullYear() === _selected.getFullYear() &&
125
+ date.getMonth() === _selected.getMonth() &&
126
+ date.getDate() === _selected.getDate())
127
+ : false;
128
+ grid.appendChild(_cell(d, isTod, isSel, false, date));
129
+ }
130
+
131
+ // Next month filler
132
+ var total = firstDay + daysInMonth;
133
+ var rem = total % 7 === 0 ? 0 : 7 - (total % 7);
134
+ for (var n = 1; n <= rem; n++) {
135
+ grid.appendChild(_cell(n, false, false, true));
136
+ }
137
+
138
+ // Time row visibility
139
+ var timeRow = el('#dp-time-row');
140
+ if (timeRow) timeRow.style.display = _mode === 'datetime' ? 'flex' : 'none';
141
+
142
+ // Reposition dropdown after content change
143
+ if (_dd) _dd.reposition();
144
+ }
145
+
146
+ function _cell(day, isToday, isSelected, isMuted, date) {
147
+ var btn = document.createElement('button');
148
+ btn.type = 'button';
149
+ btn.className = 'dp-cell' +
150
+ (isToday ? ' dp-today' : '') +
151
+ (isSelected ? ' dp-selected' : '') +
152
+ (isMuted ? ' dp-muted' : '');
153
+ btn.textContent = day;
154
+ if (date) {
155
+ btn.addEventListener('click', function () {
156
+ _selected = date;
157
+ _renderGrid(); // re-render to update selection highlight
158
+ if (_mode === 'date') {
159
+ _apply();
160
+ }
161
+ // datetime: auto-apply immediately too
162
+ _apply();
163
+ });
164
+ }
165
+ return btn;
166
+ }
167
+
168
+ function _apply() {
169
+ if (!_selected || !_target || !_trigger) return;
170
+
171
+ var y = _selected.getFullYear();
172
+ var mo = String(_selected.getMonth() + 1).padStart(2, '0');
173
+ var d = String(_selected.getDate()).padStart(2, '0');
174
+
175
+ if (_mode === 'datetime') {
176
+ var timeInput = el('#dp-time-input');
177
+ var time = (timeInput && timeInput.value) ? timeInput.value : '00:00';
178
+ _target.value = y + '-' + mo + '-' + d + 'T' + time;
179
+ _trigger.value = _formatDisplay(y + '-' + mo + '-' + d, time);
180
+ } else {
181
+ _target.value = y + '-' + mo + '-' + d;
182
+ _trigger.value = _formatDisplayDate(y, mo, d);
183
+ }
184
+
185
+ // Fire change on hidden input
186
+ _target.dispatchEvent(new Event('change', { bubbles: true }));
187
+ _close();
188
+ }
189
+
190
+ function _formatDisplayDate(y, mo, d) {
191
+ return MONTHS[parseInt(mo) - 1].slice(0, 3) + ' ' + parseInt(d) + ', ' + y;
192
+ }
193
+
194
+ function _formatDisplay(dateStr, time) {
195
+ var parts = dateStr.split('-');
196
+ return _formatDisplayDate(parts[0], parts[1], parts[2]) + ' ' + time;
197
+ }
198
+
199
+ function _close() {
200
+ if (_dd) _dd.close();
201
+ }
202
+
203
+ function el(id) {
204
+ return _panel ? _panel.querySelector(id) : null;
205
+ }
206
+
207
+ // ── Open ──────────────────────────────────────────────────────────────────
208
+
209
+ function open(triggerEl, targetEl, mode) {
210
+ _trigger = triggerEl;
211
+ _target = targetEl;
212
+ _mode = mode || 'date';
213
+
214
+ // Parse existing value
215
+ var raw = targetEl.value;
216
+ if (raw) {
217
+ var d = new Date(raw);
218
+ if (!isNaN(d.getTime())) {
219
+ _selected = d;
220
+ // Pre-fill time input for datetime
221
+ if (_mode === 'datetime') {
222
+ var h = String(d.getHours()).padStart(2, '0');
223
+ var m = String(d.getMinutes()).padStart(2, '0');
224
+ var timeEl = _panel.querySelector('#dp-time-input');
225
+ if (timeEl) timeEl.value = h + ':' + m;
226
+ }
227
+ } else {
228
+ _selected = null;
229
+ }
230
+ } else {
231
+ _selected = null;
232
+ }
233
+
234
+ // Navigate to selected month or today
235
+ var nav = _selected ? new Date(_selected) : new Date();
236
+ _current = new Date(nav.getFullYear(), nav.getMonth(), 1);
237
+
238
+ _renderGrid();
239
+
240
+ // Reposition after render so the dropdown reads the panel's actual
241
+ // height (which varies: 5-row vs 6-row month, time row visible or not)
242
+ // and flips above the anchor if there isn't enough room below.
243
+ if (_dd && _dd.isOpen()) {
244
+ _dd.reposition();
245
+ }
246
+
247
+ // Create a new dropdown each open (anchor may differ)
248
+ if (_dd) _dd.destroy();
249
+ _dd = UI.Dropdown.create({
250
+ anchor: triggerEl,
251
+ content: _panel,
252
+ placement: 'bottom-start',
253
+ offset: 4,
254
+ maxHeight: 440, // tallest month (6 rows) + shortcuts + time row ≈ 400px
255
+ onClose: function () { _dd = null; },
256
+ });
257
+ _dd.open();
258
+ }
259
+
260
+ // ── Init ──────────────────────────────────────────────────────────────────
261
+
262
+ function init() {
263
+ _panel = _buildPanel();
264
+
265
+ document.querySelectorAll('.dp-trigger').forEach(function (trigger) {
266
+ var targetId = trigger.dataset.target;
267
+ var mode = trigger.dataset.mode || 'date';
268
+ var target = document.getElementById(targetId);
269
+ if (!target) return;
270
+
271
+ // Show formatted display value on load
272
+ if (target.value) {
273
+ var d = new Date(target.value);
274
+ if (!isNaN(d.getTime())) {
275
+ if (mode === 'datetime') {
276
+ var h = String(d.getHours()).padStart(2,'0');
277
+ var m = String(d.getMinutes()).padStart(2,'0');
278
+ trigger.value = _formatDisplay(
279
+ d.getFullYear() + '-' +
280
+ String(d.getMonth()+1).padStart(2,'0') + '-' +
281
+ String(d.getDate()).padStart(2,'0'),
282
+ h + ':' + m
283
+ );
284
+ } else {
285
+ trigger.value = _formatDisplayDate(
286
+ d.getFullYear(),
287
+ String(d.getMonth()+1).padStart(2,'0'),
288
+ String(d.getDate()).padStart(2,'0')
289
+ );
290
+ }
291
+ }
292
+ }
293
+
294
+ trigger.addEventListener('click', function () {
295
+ open(trigger, target, mode);
296
+ });
297
+ trigger.addEventListener('keydown', function (e) {
298
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(trigger, target, mode); }
299
+ });
300
+
301
+ // Make it read-only so browser native picker doesn't interfere
302
+ trigger.setAttribute('readonly', 'readonly');
303
+ });
304
+ }
305
+
306
+ $(function () {
307
+ if (typeof UI === 'undefined') {
308
+ console.warn('[DatePicker] UI not loaded — date picker will not initialise');
309
+ return;
310
+ }
311
+ init();
312
+ });
313
+
314
+ root.DatePicker = { open: open, init: init };
315
+
316
+ }(jQuery, window));