Refactor new datepicker so that we can use a custom grid cell, and fix issues with timezone offsets
This commit is contained in:
parent
0aeddfa029
commit
4d24b2ba1c
|
@ -3,10 +3,11 @@
|
|||
import Select from "../../Select.svelte"
|
||||
import dayjs from "dayjs"
|
||||
import NumberInput from "./NumberInput.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let value
|
||||
export let onChange
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const DaysOfWeek = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
|
@ -58,7 +59,10 @@
|
|||
|
||||
const handleDateChange = date => {
|
||||
const base = value || now
|
||||
onChange(base.year(date.year()).month(date.month()).date(date.date()))
|
||||
dispatch(
|
||||
"change",
|
||||
base.year(date.year()).month(date.month()).date(date.date())
|
||||
)
|
||||
}
|
||||
|
||||
export const setDate = date => {
|
||||
|
@ -224,6 +228,9 @@
|
|||
.spectrum-Calendar-date.is-selected {
|
||||
color: white;
|
||||
}
|
||||
.spectrum-Calendar-dayOfWeek {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
||||
/* Style select */
|
||||
.month-selector :global(.spectrum-Picker) {
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||
import Popover from "../../../Popover/Popover.svelte"
|
||||
import dayjs from "dayjs"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import TimePicker from "./TimePicker.svelte"
|
||||
import Calendar from "./Calendar.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import DateInput from "./DateInput.svelte"
|
||||
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
||||
import { parseDate } from "../../../helpers"
|
||||
import DatePickerPopoverContents from "./DatePickerPopoverContents.svelte"
|
||||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
|
@ -25,74 +22,18 @@
|
|||
export let api = null
|
||||
export let align = "left"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let isOpen = false
|
||||
let anchor
|
||||
let popover
|
||||
let calendar
|
||||
|
||||
$: parsedValue = parseDate(value, { timeOnly, dateOnly: !enableTime })
|
||||
$: showCalendar = !timeOnly
|
||||
$: showTime = enableTime || timeOnly
|
||||
|
||||
const clearDateOnBackspace = event => {
|
||||
// Ignore if we're typing a value
|
||||
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
||||
return
|
||||
}
|
||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
||||
handleChange(null)
|
||||
popover?.hide()
|
||||
}
|
||||
}
|
||||
$: parsedValue = parseDate(value, { timeOnly, enableTime })
|
||||
|
||||
const onOpen = () => {
|
||||
isOpen = true
|
||||
if (useKeyboardShortcuts) {
|
||||
document.addEventListener("keyup", clearDateOnBackspace)
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
isOpen = false
|
||||
if (useKeyboardShortcuts) {
|
||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = date => {
|
||||
if (!date) {
|
||||
dispatch("change", null)
|
||||
return
|
||||
}
|
||||
let newValue = date.toISOString()
|
||||
|
||||
// Time only fields always ignore timezones, otherwise they make no sense.
|
||||
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
||||
// time picked, without timezone
|
||||
const offsetForTimezone = (enableTime && ignoreTimezones) || timeOnly
|
||||
if (offsetForTimezone) {
|
||||
const offset = new Date().getTimezoneOffset() * 60000
|
||||
newValue = new Date(date.valueOf() - offset).toISOString().slice(0, -1)
|
||||
}
|
||||
|
||||
// For date-only fields, construct a manual timestamp string without a time
|
||||
// or time zone
|
||||
else if (!enableTime) {
|
||||
const year = date.year()
|
||||
const month = `${date.month() + 1}`.padStart(2, "0")
|
||||
const day = `${date.date()}`.padStart(2, "0")
|
||||
newValue = `${year}-${month}-${day}T00:00:00.000`
|
||||
}
|
||||
|
||||
dispatch("change", newValue)
|
||||
}
|
||||
|
||||
const setToNow = () => {
|
||||
const now = dayjs()
|
||||
calendar?.setDate(now)
|
||||
handleChange(now)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
@ -130,54 +71,13 @@
|
|||
{align}
|
||||
>
|
||||
{#if isOpen}
|
||||
<div class="date-time-popover">
|
||||
{#if showCalendar}
|
||||
<Calendar
|
||||
<DatePickerPopoverContents
|
||||
{useKeyboardShortcuts}
|
||||
{ignoreTimezones}
|
||||
{enableTime}
|
||||
{timeOnly}
|
||||
value={parsedValue}
|
||||
onChange={handleChange}
|
||||
bind:this={calendar}
|
||||
on:change
|
||||
/>
|
||||
{/if}
|
||||
<div class="footer" class:spaced={showCalendar}>
|
||||
{#if showTime}
|
||||
<TimePicker value={parsedValue} onChange={handleChange} />
|
||||
{/if}
|
||||
<div class="actions">
|
||||
<ActionButton
|
||||
disabled={!value}
|
||||
size="S"
|
||||
on:click={() => handleChange(null)}
|
||||
>
|
||||
Clear
|
||||
</ActionButton>
|
||||
<ActionButton size="S" on:click={setToNow}>
|
||||
{showTime ? "Now" : "Today"}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.date-time-popover {
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 60px;
|
||||
}
|
||||
.footer.spaced {
|
||||
padding-top: 14px;
|
||||
}
|
||||
.actions {
|
||||
padding: 4px 0;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<script>
|
||||
import dayjs from "dayjs"
|
||||
import TimePicker from "./TimePicker.svelte"
|
||||
import Calendar from "./Calendar.svelte"
|
||||
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import { stringifyDate } from "../../../helpers"
|
||||
|
||||
export let useKeyboardShortcuts = true
|
||||
export let ignoreTimezones
|
||||
export let enableTime
|
||||
export let timeOnly
|
||||
export let value
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let calendar
|
||||
|
||||
$: showCalendar = !timeOnly
|
||||
$: showTime = enableTime || timeOnly
|
||||
|
||||
const setToNow = () => {
|
||||
const now = dayjs()
|
||||
calendar?.setDate(now)
|
||||
handleChange(now)
|
||||
}
|
||||
|
||||
const handleChange = date => {
|
||||
dispatch(
|
||||
"change",
|
||||
stringifyDate(date, { enableTime, timeOnly, ignoreTimezones })
|
||||
)
|
||||
}
|
||||
|
||||
const clearDateOnBackspace = event => {
|
||||
// Ignore if we're typing a value
|
||||
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
||||
return
|
||||
}
|
||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
||||
dispatch("change", null)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (useKeyboardShortcuts) {
|
||||
document.addEventListener("keyup", clearDateOnBackspace)
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="date-time-popover">
|
||||
{#if showCalendar}
|
||||
<Calendar
|
||||
{value}
|
||||
on:change={e => handleChange(e.detail)}
|
||||
bind:this={calendar}
|
||||
/>
|
||||
{/if}
|
||||
<div class="footer" class:spaced={showCalendar}>
|
||||
{#if showTime}
|
||||
<TimePicker {value} on:change={e => handleChange(e.detail)} />
|
||||
{/if}
|
||||
<div class="actions">
|
||||
<ActionButton
|
||||
disabled={!value}
|
||||
size="S"
|
||||
on:click={() => dispatch("change", null)}
|
||||
>
|
||||
Clear
|
||||
</ActionButton>
|
||||
<ActionButton size="S" on:click={setToNow}>
|
||||
{showTime ? "Now" : "Today"}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.date-time-popover {
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 60px;
|
||||
}
|
||||
.footer.spaced {
|
||||
padding-top: 14px;
|
||||
}
|
||||
.actions {
|
||||
padding: 4px 0;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
|
@ -33,7 +33,7 @@
|
|||
font-weight: bold;
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
box-sizing: content-box;
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
input:focus,
|
||||
input:hover {
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
import { cleanInput } from "./utils"
|
||||
import dayjs from "dayjs"
|
||||
import NumberInput from "./NumberInput.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let value
|
||||
export let onChange
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: displayValue = value || dayjs()
|
||||
|
||||
const handleHourChange = e => {
|
||||
onChange(displayValue.hour(parseInt(e.target.value)))
|
||||
dispatch("change", displayValue.hour(parseInt(e.target.value)))
|
||||
}
|
||||
|
||||
const handleMinuteChange = e => {
|
||||
onChange(displayValue.minute(parseInt(e.target.value)))
|
||||
dispatch("change", displayValue.minute(parseInt(e.target.value)))
|
||||
}
|
||||
|
||||
const cleanHour = cleanInput({ max: 23, pad: 2, fallback: "00" })
|
||||
|
@ -51,7 +53,7 @@
|
|||
.time-picker span {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
z-index: -1;
|
||||
z-index: 0;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,7 @@ export { default as CoreCombobox } from "./Combobox.svelte"
|
|||
export { default as CoreSwitch } from "./Switch.svelte"
|
||||
export { default as CoreSearch } from "./Search.svelte"
|
||||
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
|
||||
export { default as CoreDatePickerPopoverContents } from "./DatePicker/DatePickerPopoverContents.svelte"
|
||||
export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
|
||||
export { default as CoreDropzone } from "./Dropzone.svelte"
|
||||
export { default as CoreStepper } from "./Stepper.svelte"
|
||||
|
|
|
@ -117,7 +117,9 @@ export const copyToClipboard = value => {
|
|||
})
|
||||
}
|
||||
|
||||
export const parseDate = (value, { dateOnly } = {}) => {
|
||||
// Parsed a date value. This is usually an ISO string, but can be a
|
||||
// bunch of different formats and shapes depending on schema flags.
|
||||
export const parseDate = (value, { enableTime = true }) => {
|
||||
// If empty then invalid
|
||||
if (!value) {
|
||||
return null
|
||||
|
@ -131,7 +133,7 @@ export const parseDate = (value, { dateOnly } = {}) => {
|
|||
}
|
||||
|
||||
// If date only, check for cases where we received a UTC string
|
||||
else if (dateOnly && value.endsWith("Z")) {
|
||||
else if (!enableTime && value.endsWith("Z")) {
|
||||
value = value.split("Z")[0]
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +150,42 @@ export const parseDate = (value, { dateOnly } = {}) => {
|
|||
return dayjs(Math.floor(parsedDate.valueOf() / 1000) * 1000)
|
||||
}
|
||||
|
||||
export const getDateDisplayValue = (value, { enableTime, timeOnly }) => {
|
||||
// Stringifies a dayjs object to create an ISO string that respects the various
|
||||
// schema flags
|
||||
export const stringifyDate = (
|
||||
value,
|
||||
{ enableTime = true, timeOnly = false, ignoreTimezones = false }
|
||||
) => {
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Time only fields always ignore timezones, otherwise they make no sense.
|
||||
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
||||
// time picked, without timezone
|
||||
const offsetForTimezone = (enableTime && ignoreTimezones) || timeOnly
|
||||
if (offsetForTimezone) {
|
||||
// Ensure we use the correct offset for the date
|
||||
const referenceDate = timeOnly ? new Date() : value.toDate()
|
||||
const offset = referenceDate.getTimezoneOffset() * 60000
|
||||
return new Date(value.valueOf() - offset).toISOString().slice(0, -1)
|
||||
}
|
||||
|
||||
// For date-only fields, construct a manual timestamp string without a time
|
||||
// or time zone
|
||||
else if (!enableTime) {
|
||||
const year = value.year()
|
||||
const month = `${value.month() + 1}`.padStart(2, "0")
|
||||
const day = `${value.date()}`.padStart(2, "0")
|
||||
return `${year}-${month}-${day}T00:00:00.000`
|
||||
}
|
||||
}
|
||||
|
||||
// Formats a dayjs date according to schema flags
|
||||
export const getDateDisplayValue = (
|
||||
value,
|
||||
{ enableTime = true, timeOnly = false }
|
||||
) => {
|
||||
if (!value?.isValid()) {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<script>
|
||||
import { CoreDatePicker, Icon, Helpers } from "@budibase/bbui"
|
||||
import {
|
||||
CoreDatePickerPopoverContents,
|
||||
Icon,
|
||||
Helpers,
|
||||
clickOutside,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import dayjs from "dayjs"
|
||||
import { debounce } from "../../../utils/utils"
|
||||
|
||||
export let value
|
||||
export let schema
|
||||
|
@ -9,44 +16,89 @@
|
|||
export let readonly = false
|
||||
export let api
|
||||
|
||||
let datePickerAPI
|
||||
let isOpen
|
||||
|
||||
$: timeOnly = schema?.timeOnly
|
||||
$: dateOnly = schema?.dateOnly
|
||||
$: enableTime = !schema?.dateOnly
|
||||
$: ignoreTimezones = schema?.ignoreTimezones
|
||||
$: editable = focused && !readonly
|
||||
$: displayValue = getDisplayValue(value, timeOnly, dateOnly)
|
||||
$: parsedValue = Helpers.parseDate(value, {
|
||||
timeOnly,
|
||||
enableTime,
|
||||
ignoreTimezones,
|
||||
})
|
||||
$: displayValue = getDisplayValue(parsedValue, timeOnly, enableTime)
|
||||
// Ensure open state matches desired state
|
||||
$: {
|
||||
if (!focused && isOpen) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
const getDisplayValue = (value, timeOnly, dateOnly) => {
|
||||
const parsedDate = Helpers.parseDate(value, { dateOnly })
|
||||
return Helpers.getDateDisplayValue(parsedDate, {
|
||||
enableTime: !dateOnly,
|
||||
const getDisplayValue = (value, timeOnly, enableTime) => {
|
||||
return Helpers.getDateDisplayValue(value, {
|
||||
enableTime,
|
||||
timeOnly,
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure we close flatpickr when unselected
|
||||
$: {
|
||||
if (!focused) {
|
||||
datePickerAPI?.close()
|
||||
}
|
||||
const open = () => {
|
||||
isOpen = true
|
||||
}
|
||||
|
||||
const onKeyDown = () => {
|
||||
const close = () => {
|
||||
isOpen = false
|
||||
}
|
||||
|
||||
const onKeyDown = e => {
|
||||
if (!isOpen) {
|
||||
return false
|
||||
}
|
||||
e.preventDefault()
|
||||
if (e.key === "ArrowUp") {
|
||||
changeDate(-1, "week")
|
||||
} else if (e.key === "ArrowDown") {
|
||||
changeDate(1, "week")
|
||||
} else if (e.key === "ArrowLeft") {
|
||||
changeDate(-1, "day")
|
||||
} else if (e.key === "ArrowRight") {
|
||||
changeDate(1, "day")
|
||||
} else if (e.key === "Enter") {
|
||||
close()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const changeDate = (quantity, unit) => {
|
||||
if (!value) {
|
||||
value = dayjs()
|
||||
} else {
|
||||
value = dayjs(value).add(quantity, unit)
|
||||
}
|
||||
debouncedOnChange(
|
||||
Helpers.stringifyDate(value, {
|
||||
enableTime,
|
||||
timeOnly,
|
||||
ignoreTimezones,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const debouncedOnChange = debounce(onChange, 250)
|
||||
|
||||
onMount(() => {
|
||||
api = {
|
||||
onKeyDown,
|
||||
focus: () => datePickerAPI?.open(),
|
||||
blur: () => datePickerAPI?.close(),
|
||||
focus: open,
|
||||
blur: close,
|
||||
isActive: () => isOpen,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="container" class:editable on:click={editable ? open : null}>
|
||||
<div class="value">
|
||||
{displayValue}
|
||||
</div>
|
||||
|
@ -55,17 +107,14 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if editable}
|
||||
<div class="picker">
|
||||
<CoreDatePicker
|
||||
{value}
|
||||
{#if isOpen}
|
||||
<div class="picker" use:clickOutside={close}>
|
||||
<CoreDatePickerPopoverContents
|
||||
value={parsedValue}
|
||||
on:change={e => onChange(e.detail)}
|
||||
enableTime={!dateOnly}
|
||||
{enableTime}
|
||||
{timeOnly}
|
||||
ignoreTimezones={schema.ignoreTimezones}
|
||||
bind:api={datePickerAPI}
|
||||
on:open={() => (isOpen = true)}
|
||||
on:close={() => (isOpen = false)}
|
||||
{ignoreTimezones}
|
||||
useKeyboardShortcuts={false}
|
||||
/>
|
||||
</div>
|
||||
|
@ -80,6 +129,10 @@
|
|||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
gap: var(--cell-spacing);
|
||||
user-select: none;
|
||||
}
|
||||
.container.editable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.value {
|
||||
flex: 1 1 auto;
|
||||
|
@ -92,9 +145,10 @@
|
|||
}
|
||||
.picker {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
.picker :global(.spectrum-Textfield-input) {
|
||||
width: 100%;
|
||||
top: 100%;
|
||||
left: -1px;
|
||||
background: var(--grid-background-alt);
|
||||
border: var(--cell-border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -103,8 +103,8 @@
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% + var(--max-cell-render-width-overflow));
|
||||
height: calc(var(--row-height) + var(--max-cell-render-height));
|
||||
width: calc(100% + var(--max-cell-render-verflow));
|
||||
height: calc(var(--row-height) + var(--max-cell-render-overflow));
|
||||
z-index: 1;
|
||||
border-radius: 2px;
|
||||
resize: none;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
||||
$: {
|
||||
// Close when deselected
|
||||
if (!focused) {
|
||||
if (!focused && isOpen) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +219,7 @@
|
|||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
max-height: var(--max-cell-render-height);
|
||||
max-height: var(--max-cell-render-overflow);
|
||||
overflow-y: auto;
|
||||
border: var(--cell-border);
|
||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
$: lookupMap = buildLookupMap(value, isOpen)
|
||||
$: debouncedSearch(searchString)
|
||||
$: {
|
||||
if (!focused) {
|
||||
if (!focused && isOpen) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
@ -451,7 +451,7 @@
|
|||
left: 0;
|
||||
width: 100%;
|
||||
max-height: calc(
|
||||
var(--max-cell-render-height) + var(--row-height) - var(--values-height)
|
||||
var(--max-cell-render-overflow) + var(--row-height) - var(--values-height)
|
||||
);
|
||||
background: var(--grid-background-alt);
|
||||
border: var(--cell-border);
|
||||
|
|
|
@ -22,8 +22,7 @@
|
|||
import NewRow from "./NewRow.svelte"
|
||||
import { createGridWebsocket } from "../lib/websocket"
|
||||
import {
|
||||
MaxCellRenderHeight,
|
||||
MaxCellRenderWidthOverflow,
|
||||
MaxCellRenderOverflow,
|
||||
GutterWidth,
|
||||
DefaultRowHeight,
|
||||
} from "../lib/constants"
|
||||
|
@ -78,6 +77,7 @@
|
|||
contentLines,
|
||||
gridFocused,
|
||||
error,
|
||||
focusedCellId,
|
||||
} = context
|
||||
|
||||
// Keep config store up to date with props
|
||||
|
@ -129,7 +129,7 @@
|
|||
class:quiet
|
||||
on:mouseenter={() => gridFocused.set(true)}
|
||||
on:mouseleave={() => gridFocused.set(false)}
|
||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines};"
|
||||
>
|
||||
{#if showControls}
|
||||
<div class="controls">
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export const Padding = 246
|
||||
export const MaxCellRenderHeight = 222
|
||||
export const ScrollBarSize = 8
|
||||
export const GutterWidth = 72
|
||||
export const DefaultColumnWidth = 200
|
||||
|
@ -12,4 +11,4 @@ export const NewRowID = "new"
|
|||
export const BlankRowID = "blank"
|
||||
export const RowPageSize = 100
|
||||
export const FocusedCellMinOffset = 48
|
||||
export const MaxCellRenderWidthOverflow = Padding - 3 * ScrollBarSize
|
||||
export const MaxCellRenderOverflow = Padding - 3 * ScrollBarSize
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { derived } from "svelte/store"
|
||||
import {
|
||||
MaxCellRenderHeight,
|
||||
MaxCellRenderWidthOverflow,
|
||||
MaxCellRenderOverflow,
|
||||
MinColumnWidth,
|
||||
ScrollBarSize,
|
||||
} from "../lib/constants"
|
||||
|
@ -95,11 +94,11 @@ export const deriveStores = context => {
|
|||
|
||||
// Compute the last row index with space to render popovers below it
|
||||
const minBottom =
|
||||
$height - ScrollBarSize * 3 - MaxCellRenderHeight + offset
|
||||
$height - ScrollBarSize * 3 - MaxCellRenderOverflow + offset
|
||||
const lastIdx = Math.floor(minBottom / $rowHeight)
|
||||
|
||||
// Compute the first row index with space to render popovers above it
|
||||
const minTop = MaxCellRenderHeight + offset
|
||||
const minTop = MaxCellRenderOverflow + offset
|
||||
const firstIdx = Math.ceil(minTop / $rowHeight)
|
||||
|
||||
// Use the greater of the two indices so that we prefer content below,
|
||||
|
@ -117,7 +116,7 @@ export const deriveStores = context => {
|
|||
let inversionIdx = $visibleColumns.length
|
||||
for (let i = $visibleColumns.length - 1; i >= 0; i--, inversionIdx--) {
|
||||
const rightEdge = $visibleColumns[i].left + $visibleColumns[i].width
|
||||
if (rightEdge + MaxCellRenderWidthOverflow <= cutoff) {
|
||||
if (rightEdge + MaxCellRenderOverflow <= cutoff) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue