Basic more event handling for month view when there are too many events in a day given its height
This commit is contained in:
parent
25515eccb9
commit
5f4cffd5aa
@ -26,6 +26,7 @@
|
|||||||
/* day element */
|
/* day element */
|
||||||
li {
|
li {
|
||||||
@apply bg-white relative px-1 pt-8 border-t-md border-gray-900 overflow-y-auto;
|
@apply bg-white relative px-1 pt-8 border-t-md border-gray-900 overflow-y-auto;
|
||||||
|
transition: scale 150ms ease-in-out;
|
||||||
|
|
||||||
/* day number */
|
/* day number */
|
||||||
&::before {
|
&::before {
|
||||||
@ -54,6 +55,60 @@
|
|||||||
@apply rounded-br-lg;
|
@apply rounded-br-lg;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
/* progressive "show more" button */
|
||||||
|
div.more-events {
|
||||||
|
@apply absolute bottom-0 h-6 bg-inherit z-2 flex items-center;
|
||||||
|
width: calc(100% - 0.5rem);
|
||||||
|
}
|
||||||
|
button.day-more {
|
||||||
|
@apply text-xs px-1 h-4;
|
||||||
|
}
|
||||||
|
&[data-event-visible="0"] {
|
||||||
|
.event:nth-child(n+1) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="1"] {
|
||||||
|
.event:nth-child(n+2) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="2"] {
|
||||||
|
.event:nth-child(n+3) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="3"] {
|
||||||
|
.event:nth-child(n+4) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="4"] {
|
||||||
|
.event:nth-child(n+5) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="5"] {
|
||||||
|
.event:nth-child(n+6) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="6"] {
|
||||||
|
.event:nth-child(n+7) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="7"] {
|
||||||
|
.event:nth-child(n+8) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="8"] {
|
||||||
|
.event:nth-child(n+9) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&[data-event-visible="9"] {
|
||||||
|
.event:nth-child(n+10) { @apply hidden; }
|
||||||
|
}
|
||||||
|
&.is-expanded {
|
||||||
|
position: relative;
|
||||||
|
height: min-content;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
z-index: 3;
|
||||||
|
border: 1.5px solid black;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
scale: 1.05;
|
||||||
|
width: 120%;
|
||||||
|
margin-left: -10%;
|
||||||
|
|
||||||
|
div.more-events {
|
||||||
|
@apply relative h-8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* events */
|
/* events */
|
||||||
.event {
|
.event {
|
||||||
@apply flex items-center text-xs gap-1 px-1 py-px font-medium truncate rounded-sm bg-transparent;
|
@apply flex items-center text-xs gap-1 px-1 py-px font-medium truncate rounded-sm bg-transparent;
|
||||||
|
|||||||
@ -9,6 +9,10 @@ const SELECTORS = {
|
|||||||
colorPickerColor: '[data-colorpicker-color]',
|
colorPickerColor: '[data-colorpicker-color]',
|
||||||
colorPickerHex: '[data-colorpicker-hex]',
|
colorPickerHex: '[data-colorpicker-hex]',
|
||||||
colorPickerRandom: '[data-colorpicker-random]',
|
colorPickerRandom: '[data-colorpicker-random]',
|
||||||
|
monthDay: '.calendar.month .day',
|
||||||
|
monthDayEvent: 'a.event',
|
||||||
|
monthDayMore: '[data-day-more]',
|
||||||
|
monthDayMoreWrap: '.more-events',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,8 +197,127 @@ function initColorPickers(root = document) {
|
|||||||
root.querySelectorAll(SELECTORS.colorPicker).forEach(wire);
|
root.querySelectorAll(SELECTORS.colorPicker).forEach(wire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* month view overflow handling (progressive enhancement)
|
||||||
|
*/
|
||||||
|
function initMonthOverflow(root = document) {
|
||||||
|
const days = root.querySelectorAll(SELECTORS.monthDay);
|
||||||
|
days.forEach((day) => updateMonthOverflow(day));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDayMoreButton(dayEl) {
|
||||||
|
let wrapper = dayEl.querySelector(SELECTORS.monthDayMoreWrap);
|
||||||
|
if (!wrapper) {
|
||||||
|
wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'more-events';
|
||||||
|
dayEl.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = wrapper.querySelector(SELECTORS.monthDayMore);
|
||||||
|
if (!button) {
|
||||||
|
button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.className = 'day-more hidden';
|
||||||
|
button.setAttribute('data-day-more', '');
|
||||||
|
wrapper.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoreLabel(dayEl, count) {
|
||||||
|
const template = dayEl.getAttribute('data-more-label') || ':count more';
|
||||||
|
return template.replace(':count', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lessLabel(dayEl) {
|
||||||
|
return dayEl.getAttribute('data-less-label') || 'Show less';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMonthOverflow(dayEl) {
|
||||||
|
if (!dayEl) return;
|
||||||
|
|
||||||
|
const events = Array.from(dayEl.querySelectorAll(SELECTORS.monthDayEvent))
|
||||||
|
.filter((el) => !el.classList.contains('hidden'));
|
||||||
|
|
||||||
|
const moreButton = ensureDayMoreButton(dayEl);
|
||||||
|
|
||||||
|
if (!events.length) {
|
||||||
|
moreButton.textContent = '';
|
||||||
|
moreButton.classList.add('hidden');
|
||||||
|
moreButton.removeAttribute('aria-expanded');
|
||||||
|
dayEl.classList.remove('day--event-overflow');
|
||||||
|
dayEl.setAttribute('data-event-visible', '0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayEl.classList.contains('is-expanded')) {
|
||||||
|
moreButton.textContent = lessLabel(dayEl);
|
||||||
|
moreButton.classList.remove('hidden');
|
||||||
|
moreButton.setAttribute('aria-expanded', 'true');
|
||||||
|
dayEl.classList.remove('day--event-overflow');
|
||||||
|
dayEl.setAttribute('data-event-visible', String(events.length));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = moreButton.closest(SELECTORS.monthDayMoreWrap);
|
||||||
|
let wrapperHeight = wrapper ? wrapper.getBoundingClientRect().height : 0;
|
||||||
|
if (wrapperHeight === 0 && wrapper) {
|
||||||
|
const wasHidden = moreButton.classList.contains('hidden');
|
||||||
|
const prevVisibility = moreButton.style.visibility;
|
||||||
|
|
||||||
|
if (wasHidden) {
|
||||||
|
moreButton.classList.remove('hidden');
|
||||||
|
moreButton.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapperHeight = wrapper.getBoundingClientRect().height || 0;
|
||||||
|
|
||||||
|
if (wasHidden) {
|
||||||
|
moreButton.classList.add('hidden');
|
||||||
|
moreButton.style.visibility = prevVisibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevVisibility = dayEl.style.visibility;
|
||||||
|
dayEl.style.visibility = 'hidden';
|
||||||
|
dayEl.removeAttribute('data-event-visible');
|
||||||
|
dayEl.classList.remove('day--event-overflow');
|
||||||
|
|
||||||
|
const availableHeight = dayEl.clientHeight - wrapperHeight;
|
||||||
|
let hiddenCount = 0;
|
||||||
|
events.forEach((eventEl) => {
|
||||||
|
const bottom = eventEl.offsetTop + eventEl.offsetHeight;
|
||||||
|
if (bottom > availableHeight + 0.5) {
|
||||||
|
hiddenCount += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dayEl.style.visibility = prevVisibility;
|
||||||
|
|
||||||
|
const visibleCount = Math.max(0, events.length - hiddenCount);
|
||||||
|
dayEl.setAttribute('data-event-visible', String(visibleCount));
|
||||||
|
|
||||||
|
if (hiddenCount > 0) {
|
||||||
|
moreButton.textContent = formatMoreLabel(dayEl, hiddenCount);
|
||||||
|
moreButton.classList.remove('hidden');
|
||||||
|
moreButton.setAttribute('aria-expanded', 'false');
|
||||||
|
dayEl.classList.add('day--event-overflow');
|
||||||
|
} else {
|
||||||
|
moreButton.textContent = '';
|
||||||
|
moreButton.classList.add('hidden');
|
||||||
|
moreButton.removeAttribute('aria-expanded');
|
||||||
|
dayEl.classList.remove('day--event-overflow');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialization
|
||||||
|
*/
|
||||||
|
|
||||||
function initUI() {
|
function initUI() {
|
||||||
initColorPickers();
|
initColorPickers();
|
||||||
|
initMonthOverflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial bind
|
// initial bind
|
||||||
@ -203,4 +326,23 @@ document.addEventListener('DOMContentLoaded', initUI);
|
|||||||
// rebind in htmx for swapped content
|
// rebind in htmx for swapped content
|
||||||
document.addEventListener('htmx:afterSwap', (e) => {
|
document.addEventListener('htmx:afterSwap', (e) => {
|
||||||
initColorPickers(e.target);
|
initColorPickers(e.target);
|
||||||
|
initMonthOverflow(e.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const button = event.target.closest(SELECTORS.monthDayMore);
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const dayEl = button.closest(SELECTORS.monthDay);
|
||||||
|
if (!dayEl) return;
|
||||||
|
|
||||||
|
dayEl.classList.toggle('is-expanded');
|
||||||
|
updateMonthOverflow(dayEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
let monthResizeTimer;
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (!document.querySelector(SELECTORS.monthDay)) return;
|
||||||
|
window.clearTimeout(monthResizeTimer);
|
||||||
|
monthResizeTimer = window.setTimeout(() => initMonthOverflow(), 100);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user