@r2digisolutions/components 0.8.6 → 0.8.8
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.
|
@@ -91,9 +91,13 @@
|
|
|
91
91
|
|
|
92
92
|
let triggerEl = $state<HTMLElement | null>(null);
|
|
93
93
|
let panelEl = $state<HTMLDivElement | null>(null);
|
|
94
|
-
|
|
94
|
+
/** Hide the native popover until top/left are applied (UA default is 0,0). */
|
|
95
|
+
let placed = $state(false);
|
|
95
96
|
let activeField = $state<'start' | 'end' | 'value'>('value');
|
|
96
97
|
let panelId = $state('');
|
|
98
|
+
/** In-progress range while the popover is open. Parent props stay put until both days exist. */
|
|
99
|
+
let draftStart = $state('');
|
|
100
|
+
let draftEnd = $state('');
|
|
97
101
|
|
|
98
102
|
$effect(() => {
|
|
99
103
|
panelId ||= createId('datepicker');
|
|
@@ -110,6 +114,9 @@
|
|
|
110
114
|
});
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
const shownStart = $derived(open && mode === 'range' ? draftStart : start);
|
|
118
|
+
const shownEnd = $derived(open && mode === 'range' ? draftEnd : end);
|
|
119
|
+
|
|
113
120
|
const fieldLabel = $derived.by(() => {
|
|
114
121
|
if (mode === 'single') return value ? formatIso(value) : placeholder;
|
|
115
122
|
if (mode === 'multiple') {
|
|
@@ -117,8 +124,8 @@
|
|
|
117
124
|
if (values.length === 1) return formatIso(values[0]);
|
|
118
125
|
return `${values.length} dates`;
|
|
119
126
|
}
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
127
|
+
if (shownStart && shownEnd) return `${formatIso(shownStart)} → ${formatIso(shownEnd)}`;
|
|
128
|
+
if (shownStart) return `${formatIso(shownStart)} → …`;
|
|
122
129
|
return placeholder;
|
|
123
130
|
});
|
|
124
131
|
|
|
@@ -128,9 +135,15 @@
|
|
|
128
135
|
return { side, align: align ?? (months === 2 ? 'center' : 'start') };
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
function estimatePanelSize() {
|
|
139
|
+
const monthW = months === 2 ? 288 : 320;
|
|
140
|
+
const panelW = months === 2 ? monthW * 2 + 24 : monthW + 16;
|
|
141
|
+
return { panelW, panelH: 360 };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function positionPanel(opts: { measure?: boolean; show?: boolean } = {}) {
|
|
145
|
+
if (!triggerEl) return;
|
|
146
|
+
if (opts.show) placed = true;
|
|
134
147
|
|
|
135
148
|
const trigger = triggerEl.getBoundingClientRect();
|
|
136
149
|
if (trigger.width < 2 && trigger.height < 2) return;
|
|
@@ -142,9 +155,16 @@
|
|
|
142
155
|
const viewTop = vv?.offsetTop ?? 0;
|
|
143
156
|
const gap = 8;
|
|
144
157
|
const pad = 8;
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const
|
|
158
|
+
const estimated = estimatePanelSize();
|
|
159
|
+
const canMeasure = opts.measure !== false && !!panelEl?.matches(':popover-open');
|
|
160
|
+
const panelW = Math.min(
|
|
161
|
+
canMeasure && panelEl?.offsetWidth ? panelEl.offsetWidth : estimated.panelW,
|
|
162
|
+
viewW - pad * 2
|
|
163
|
+
);
|
|
164
|
+
const panelH = Math.min(
|
|
165
|
+
canMeasure && panelEl?.offsetHeight ? panelEl.offsetHeight : estimated.panelH,
|
|
166
|
+
420
|
|
167
|
+
);
|
|
148
168
|
|
|
149
169
|
const fixed = parsePlacement(placement);
|
|
150
170
|
let side: 'top' | 'bottom' = fixed?.side ?? 'bottom';
|
|
@@ -167,15 +187,19 @@
|
|
|
167
187
|
left = Math.min(Math.max(viewLeft + pad, left), viewLeft + viewW - panelW - pad);
|
|
168
188
|
top = Math.min(Math.max(viewTop + pad, top), viewTop + viewH - panelH - pad);
|
|
169
189
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
|
|
190
|
+
// Imperative styles in `beforetoggle`: a Svelte `style={}` binding
|
|
191
|
+
// flushes too late and the UA popover paints at 0,0 first.
|
|
192
|
+
if (panelEl) {
|
|
193
|
+
const s = panelEl.style;
|
|
194
|
+
s.setProperty('margin', '0');
|
|
195
|
+
s.setProperty('inset', 'auto');
|
|
196
|
+
s.setProperty('top', `${Math.round(top)}px`);
|
|
197
|
+
s.setProperty('left', `${Math.round(left)}px`);
|
|
198
|
+
s.setProperty('right', 'auto');
|
|
199
|
+
s.setProperty('bottom', 'auto');
|
|
200
|
+
s.setProperty('width', 'max-content');
|
|
201
|
+
s.setProperty('visibility', placed ? 'visible' : 'hidden');
|
|
202
|
+
}
|
|
179
203
|
}
|
|
180
204
|
|
|
181
205
|
function schedulePosition() {
|
|
@@ -183,7 +207,7 @@
|
|
|
183
207
|
positionPanel();
|
|
184
208
|
requestAnimationFrame(() => {
|
|
185
209
|
positionPanel();
|
|
186
|
-
requestAnimationFrame(positionPanel);
|
|
210
|
+
requestAnimationFrame(() => positionPanel({ show: true }));
|
|
187
211
|
});
|
|
188
212
|
});
|
|
189
213
|
}
|
|
@@ -205,15 +229,35 @@
|
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
function handleBeforeToggle(event: ToggleEvent) {
|
|
208
|
-
if (event.newState === 'open'
|
|
209
|
-
|
|
232
|
+
if (event.newState === 'open') {
|
|
233
|
+
if (disabled) {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
beginRangeDraft();
|
|
238
|
+
placed = false;
|
|
239
|
+
// Set top/left before the first paint — UA popover defaults to 0,0.
|
|
240
|
+
positionPanel({ measure: false });
|
|
241
|
+
} else {
|
|
242
|
+
placed = false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function beginRangeDraft() {
|
|
247
|
+
draftStart = start;
|
|
248
|
+
draftEnd = end;
|
|
249
|
+
// Opening "Hasta" with a complete range: keep start, wait for a new end.
|
|
250
|
+
if (mode === 'range' && activeField === 'end' && start && end) {
|
|
251
|
+
draftEnd = '';
|
|
210
252
|
}
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
function handleToggle(event: ToggleEvent) {
|
|
214
256
|
const next = event.newState === 'open';
|
|
257
|
+
if (next) beginRangeDraft();
|
|
215
258
|
open = next;
|
|
216
259
|
if (next) schedulePosition();
|
|
260
|
+
else placed = false;
|
|
217
261
|
}
|
|
218
262
|
|
|
219
263
|
$effect(() => {
|
|
@@ -241,17 +285,23 @@
|
|
|
241
285
|
start: string;
|
|
242
286
|
end: string;
|
|
243
287
|
}) {
|
|
288
|
+
if (mode === 'range') {
|
|
289
|
+
// Keep the in-progress day local. Parent (URL/filters) only gets a complete range.
|
|
290
|
+
if (detail.start && detail.end) {
|
|
291
|
+
start = detail.start;
|
|
292
|
+
end = detail.end;
|
|
293
|
+
onchange?.(detail);
|
|
294
|
+
if (closeOnSelect) setOpen(false);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
244
299
|
onchange?.(detail);
|
|
245
300
|
|
|
246
301
|
if (!closeOnSelect) return;
|
|
247
302
|
|
|
248
303
|
if (mode === 'single' && detail.value) {
|
|
249
304
|
setOpen(false);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (mode === 'range' && detail.start && detail.end) {
|
|
254
|
-
setOpen(false);
|
|
255
305
|
}
|
|
256
306
|
}
|
|
257
307
|
|
|
@@ -313,8 +363,8 @@
|
|
|
313
363
|
]}
|
|
314
364
|
>
|
|
315
365
|
<span class="text-[11px] font-medium tracking-wide text-muted uppercase">{startLabel}</span>
|
|
316
|
-
<span class={['truncate text-sm',
|
|
317
|
-
{
|
|
366
|
+
<span class={['truncate text-sm', shownStart ? 'text-primary' : 'text-muted']}>
|
|
367
|
+
{shownStart ? formatIso(shownStart) : startPlaceholder}
|
|
318
368
|
</span>
|
|
319
369
|
</button>
|
|
320
370
|
<div class="w-px self-stretch bg-border" aria-hidden="true"></div>
|
|
@@ -336,8 +386,8 @@
|
|
|
336
386
|
]}
|
|
337
387
|
>
|
|
338
388
|
<span class="text-[11px] font-medium tracking-wide text-muted uppercase">{endLabel}</span>
|
|
339
|
-
<span class={['truncate text-sm',
|
|
340
|
-
{
|
|
389
|
+
<span class={['truncate text-sm', shownEnd ? 'text-primary' : 'text-muted']}>
|
|
390
|
+
{shownEnd ? formatIso(shownEnd) : endPlaceholder}
|
|
341
391
|
</span>
|
|
342
392
|
</button>
|
|
343
393
|
{#if hasValue && !disabled}
|
|
@@ -427,13 +477,13 @@
|
|
|
427
477
|
aria-label="Choose date"
|
|
428
478
|
onbeforetoggle={handleBeforeToggle}
|
|
429
479
|
ontoggle={handleToggle}
|
|
430
|
-
|
|
480
|
+
data-placed={placed ? true : undefined}
|
|
431
481
|
class={[
|
|
432
482
|
'datepicker-popover m-0 w-max rounded-2xl border border-border bg-surface-elevated p-2 shadow-xl outline-none',
|
|
433
483
|
months === 2 ? 'max-w-[min(42rem,calc(100vw-1rem))]' : 'max-w-[20rem]'
|
|
434
484
|
]}
|
|
435
485
|
>
|
|
436
|
-
{#key
|
|
486
|
+
{#key open}
|
|
437
487
|
{#if mode === 'single'}
|
|
438
488
|
<Calendar
|
|
439
489
|
mode="single"
|
|
@@ -466,8 +516,8 @@
|
|
|
466
516
|
<Calendar
|
|
467
517
|
mode="range"
|
|
468
518
|
{months}
|
|
469
|
-
bind:start
|
|
470
|
-
bind:end
|
|
519
|
+
bind:start={draftStart}
|
|
520
|
+
bind:end={draftEnd}
|
|
471
521
|
{min}
|
|
472
522
|
{max}
|
|
473
523
|
{disabledDates}
|
|
@@ -496,6 +546,11 @@
|
|
|
496
546
|
display: block;
|
|
497
547
|
}
|
|
498
548
|
|
|
549
|
+
.datepicker-popover:popover-open:not([data-placed]) {
|
|
550
|
+
visibility: hidden;
|
|
551
|
+
pointer-events: none;
|
|
552
|
+
}
|
|
553
|
+
|
|
499
554
|
.datepicker-popover:not(:popover-open) {
|
|
500
555
|
display: none;
|
|
501
556
|
}
|