@r2digisolutions/components 0.8.6 → 0.8.7

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,7 +91,8 @@
91
91
 
92
92
  let triggerEl = $state<HTMLElement | null>(null);
93
93
  let panelEl = $state<HTMLDivElement | null>(null);
94
- let panelStyle = $state('margin:0;inset:auto;');
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('');
97
98
 
@@ -128,9 +129,15 @@
128
129
  return { side, align: align ?? (months === 2 ? 'center' : 'start') };
129
130
  }
130
131
 
131
- function positionPanel() {
132
- if (!triggerEl || !panelEl) return;
133
- if (!panelEl.matches(':popover-open')) return;
132
+ function estimatePanelSize() {
133
+ const monthW = months === 2 ? 288 : 320;
134
+ const panelW = months === 2 ? monthW * 2 + 24 : monthW + 16;
135
+ return { panelW, panelH: 360 };
136
+ }
137
+
138
+ function positionPanel(opts: { measure?: boolean; show?: boolean } = {}) {
139
+ if (!triggerEl) return;
140
+ if (opts.show) placed = true;
134
141
 
135
142
  const trigger = triggerEl.getBoundingClientRect();
136
143
  if (trigger.width < 2 && trigger.height < 2) return;
@@ -142,9 +149,16 @@
142
149
  const viewTop = vv?.offsetTop ?? 0;
143
150
  const gap = 8;
144
151
  const pad = 8;
145
-
146
- const panelW = Math.min(panelEl.offsetWidth || 320, viewW - pad * 2);
147
- const panelH = Math.min(panelEl.offsetHeight || 360, 420);
152
+ const estimated = estimatePanelSize();
153
+ const canMeasure = opts.measure !== false && !!panelEl?.matches(':popover-open');
154
+ const panelW = Math.min(
155
+ canMeasure && panelEl?.offsetWidth ? panelEl.offsetWidth : estimated.panelW,
156
+ viewW - pad * 2
157
+ );
158
+ const panelH = Math.min(
159
+ canMeasure && panelEl?.offsetHeight ? panelEl.offsetHeight : estimated.panelH,
160
+ 420
161
+ );
148
162
 
149
163
  const fixed = parsePlacement(placement);
150
164
  let side: 'top' | 'bottom' = fixed?.side ?? 'bottom';
@@ -167,15 +181,19 @@
167
181
  left = Math.min(Math.max(viewLeft + pad, left), viewLeft + viewW - panelW - pad);
168
182
  top = Math.min(Math.max(viewTop + pad, top), viewTop + viewH - panelH - pad);
169
183
 
170
- panelStyle = [
171
- 'margin:0',
172
- 'inset:auto',
173
- `top:${Math.round(top)}px`,
174
- `left:${Math.round(left)}px`,
175
- 'right:auto',
176
- 'bottom:auto',
177
- 'width:max-content'
178
- ].join(';');
184
+ // Imperative styles in `beforetoggle`: a Svelte `style={}` binding
185
+ // flushes too late and the UA popover paints at 0,0 first.
186
+ if (panelEl) {
187
+ const s = panelEl.style;
188
+ s.setProperty('margin', '0');
189
+ s.setProperty('inset', 'auto');
190
+ s.setProperty('top', `${Math.round(top)}px`);
191
+ s.setProperty('left', `${Math.round(left)}px`);
192
+ s.setProperty('right', 'auto');
193
+ s.setProperty('bottom', 'auto');
194
+ s.setProperty('width', 'max-content');
195
+ s.setProperty('visibility', placed ? 'visible' : 'hidden');
196
+ }
179
197
  }
180
198
 
181
199
  function schedulePosition() {
@@ -183,7 +201,7 @@
183
201
  positionPanel();
184
202
  requestAnimationFrame(() => {
185
203
  positionPanel();
186
- requestAnimationFrame(positionPanel);
204
+ requestAnimationFrame(() => positionPanel({ show: true }));
187
205
  });
188
206
  });
189
207
  }
@@ -205,8 +223,16 @@
205
223
  }
206
224
 
207
225
  function handleBeforeToggle(event: ToggleEvent) {
208
- if (event.newState === 'open' && disabled) {
209
- event.preventDefault();
226
+ if (event.newState === 'open') {
227
+ if (disabled) {
228
+ event.preventDefault();
229
+ return;
230
+ }
231
+ placed = false;
232
+ // Set top/left before the first paint — UA popover defaults to 0,0.
233
+ positionPanel({ measure: false });
234
+ } else {
235
+ placed = false;
210
236
  }
211
237
  }
212
238
 
@@ -214,6 +240,7 @@
214
240
  const next = event.newState === 'open';
215
241
  open = next;
216
242
  if (next) schedulePosition();
243
+ else placed = false;
217
244
  }
218
245
 
219
246
  $effect(() => {
@@ -427,7 +454,7 @@
427
454
  aria-label="Choose date"
428
455
  onbeforetoggle={handleBeforeToggle}
429
456
  ontoggle={handleToggle}
430
- style={panelStyle}
457
+ data-placed={placed ? true : undefined}
431
458
  class={[
432
459
  'datepicker-popover m-0 w-max rounded-2xl border border-border bg-surface-elevated p-2 shadow-xl outline-none',
433
460
  months === 2 ? 'max-w-[min(42rem,calc(100vw-1rem))]' : 'max-w-[20rem]'
@@ -496,6 +523,11 @@
496
523
  display: block;
497
524
  }
498
525
 
526
+ .datepicker-popover:popover-open:not([data-placed]) {
527
+ visibility: hidden;
528
+ pointer-events: none;
529
+ }
530
+
499
531
  .datepicker-popover:not(:popover-open) {
500
532
  display: none;
501
533
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",