Skip to content

Commit 332cc41

Browse files
Add dynamic postioning date popup (#64)
Co-authored-by: Kasper <[email protected]>
1 parent 87f7587 commit 332cc41

File tree

4 files changed

+79
-15
lines changed

4 files changed

+79
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## Next
4+
- Add `dynamicPositioning` prop to avoid the date popup appearing outside the screen (@stinger567)
45
- `DateInput`: Avoid errors when `undefined` is passed as value
56

67
## 2.6.0 - 2023 Jul 11

src/lib/DateInput.svelte

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,60 @@
127127
visible = false
128128
}
129129
}
130+
131+
/** Automatically adjust date popup position to not appear outside the screen */
132+
export let dynamicPositioning = false
133+
134+
let InputElement: HTMLInputElement
135+
let pickerElement: HTMLElement | null
136+
let showAbove = false
137+
let pickerLeftPosition: number | null = null
138+
139+
function setDatePickerPosition() {
140+
// Defaults
141+
showAbove = false
142+
pickerLeftPosition = null
143+
144+
if (visible && pickerElement && dynamicPositioning) {
145+
// The child of the dateField is what is visually seen, all calculations should use this to make sure they line up properly
146+
const inputRect = InputElement.getBoundingClientRect()
147+
const horizontalOverflow = pickerElement.offsetWidth - inputRect.width
148+
149+
const bottomThreshold = inputRect.bottom + pickerElement.offsetHeight + 5
150+
const rightThreshold = inputRect.left + pickerElement.offsetWidth + 5
151+
152+
if (bottomThreshold > window.innerHeight) {
153+
// If .date-time-field is on the bottom half of the screen, open above
154+
showAbove = true
155+
}
156+
if (rightThreshold > window.innerWidth) {
157+
// If date-time-field is on the right of the screen, open to the left
158+
pickerLeftPosition = -horizontalOverflow
159+
160+
if (inputRect.left < horizontalOverflow + 5) {
161+
// If it would overflow on the left too, open in the middle of the screen
162+
const windowCenterPos = window.innerWidth / 2
163+
const newPos = windowCenterPos - pickerElement.offsetWidth / 2
164+
pickerLeftPosition = newPos - inputRect.left
165+
}
166+
}
167+
}
168+
}
169+
170+
function flyAutoPosition(node: HTMLElement) {
171+
setDatePickerPosition()
172+
return fly(node, {
173+
duration: 200,
174+
easing: cubicInOut,
175+
y: showAbove ? 5 : -5,
176+
})
177+
}
130178
</script>
131179

132180
<!-- svelte-ignore a11y-no-static-element-interactions -->
133181
<div class="date-time-field {classes}" on:focusout={onFocusOut} on:keydown={keydown}>
134182
<input
183+
bind:this={InputElement}
135184
class:invalid={!valid}
136185
type="text"
137186
value={text}
@@ -157,7 +206,14 @@
157206
}}
158207
/>
159208
{#if visible && !disabled}
160-
<div class="picker" class:visible transition:fly={{ duration: 80, easing: cubicInOut, y: -5 }}>
209+
<div
210+
class="picker"
211+
class:visible
212+
class:above={showAbove}
213+
transition:flyAutoPosition
214+
bind:this={pickerElement}
215+
style:--picker-left-position="{pickerLeftPosition}px"
216+
>
161217
<DateTimePicker
162218
on:focusout={onFocusOut}
163219
on:select={onSelect}
@@ -200,7 +256,10 @@
200256
.picker
201257
display: none
202258
position: absolute
203-
margin-top: 1px
259+
padding: 1px
260+
&.above
261+
bottom: 100%
262+
left: var(--picker-left-position)
204263
z-index: 10
205264
&.visible
206265
display: block

src/routes/DateInput.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
let closeOnSelection: boolean
1414
let browseWithoutSelecting: boolean
1515
let format: string
16+
let dynamicPositioning: boolean = true
1617
</script>
1718

1819
<Split>
@@ -28,6 +29,7 @@
2829
bind:disabled
2930
bind:closeOnSelection
3031
bind:browseWithoutSelecting
32+
bind:dynamicPositioning
3133
/>
3234

3335
<svelte:fragment slot="right">
@@ -42,6 +44,7 @@
4244
<Prop label="disabled" bind:value={disabled} />
4345
<Prop label="closeOnSelection" bind:value={closeOnSelection} />
4446
<Prop label="browseWithoutSelecting" bind:value={browseWithoutSelecting} />
47+
<Prop label="dynamicPositioning" bind:value={dynamicPositioning} />
4548
<Prop label="locale">Default</Prop>
4649
</svelte:fragment>
4750
</Split>

src/routes/docs/+page.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,20 @@ The component will not assign a date value until a specific date is selected in
2929

3030
### <a id="props" />Props
3131

32-
| Prop | Type | Description |
33-
| :----------------------- | :----------- | :---------------------------------------------------- |
34-
| `value` | Date \| null | Date value |
35-
| `min` | Date | The earliest value the user can select |
36-
| `max` | Date | The latest value the user can select |
37-
| `placeholder` | string | Placeholder used when date value is null |
38-
| `valid` | bool | Whether the text is valid |
39-
| `format` | string | Format string |
40-
| `visible` | bool | Whether the date popup is visible |
41-
| `disabled` | bool | Disable the input |
42-
| `closeOnSelection` | bool | Close the date popup when a date is selected |
43-
| `browseWithoutSelecting` | bool | Wait with updating the date until a value is selected |
44-
| `locale` | Locale | Locale object for internationalization |
32+
| Prop | Type | Description |
33+
| :----------------------- | :----------- | :---------------------------------------------------------- |
34+
| `value` | Date \| null | Date value |
35+
| `min` | Date | The earliest value the user can select |
36+
| `max` | Date | The latest value the user can select |
37+
| `placeholder` | string | Placeholder used when date value is null |
38+
| `valid` | bool | Whether the text is valid |
39+
| `format` | string | Format string |
40+
| `visible` | bool | Whether the date popup is visible |
41+
| `disabled` | bool | Disable the input |
42+
| `closeOnSelection` | bool | Close the date popup when a date is selected |
43+
| `browseWithoutSelecting` | bool | Wait with updating the date until a value is selected |
44+
| `dynamicPositioning` | bool | Dynamicly postions the date popup to best fit on the screen |
45+
| `locale` | Locale | Locale object for internationalization |
4546

4647
#### <a id="format-string" />Format string
4748

0 commit comments

Comments
 (0)