fix(calendar): убрать «г.» из подписей дней в сетке
В 0.0.4 «г.» убрали из подписи периода, но дату дня FullCalendar
подставляет ещё и в служебную подпись ячейки (`aria-label`, у navLinks —
`title`): видна как всплывающая подсказка и в accessibility-дереве.
Формат этой подписи библиотека наружу не отдаёт — `buildNavLinkAttrs`
форматирует дату жёстко зашитым `{year,month:'long',day:'numeric'}`, а
локаль `ru` всегда даёт «20 июля 2026 г.», — поэтому чистим уже
отрендеренный атрибут.
Чистка идёт через MutationObserver, а не через `dayCellDidMount`/
`dayHeaderDidMount`: при переключении недельного вида на месячный
FullCalendar переиспользует смонтированные ячейки, `didMount` для них
повторно не зовётся, и «г.» возвращалась в строке переиспользованных ячеек
(поймано на живой проверке).
Проверено в обоих видах и при навигации вперёд/назад/«Сегодня»: подписей с
«г.» в документе не остаётся.
This commit is contained in:
@@ -98,6 +98,57 @@ function renderEventContent(arg: EventContentArg) {
|
||||
/** «г.» — сноска Intl.DateTimeFormat('ru-RU', ...) после года при формате year+month(+day); в подписи периода не нужна. */
|
||||
const YEAR_SUFFIX_RE = /\s*г\.(?=\s|$)/g
|
||||
|
||||
/**
|
||||
* Подписи дней в самой сетке, из которых надо убрать «г.» (см.
|
||||
* `useStripYearSuffix`): месячный вид — номер дня в ячейке, недельный —
|
||||
* подпись колонки дня.
|
||||
*/
|
||||
const DAY_LABEL_SELECTOR = '.fc-daygrid-day-number, .fc-col-header-cell-cushion'
|
||||
|
||||
/** Убирает «г.» из `aria-label`/`title` элемента; ничего не пишет, если менять нечего. */
|
||||
function stripYearSuffixAttrs(el: Element) {
|
||||
for (const attr of ['aria-label', 'title']) {
|
||||
const value = el.getAttribute(attr)
|
||||
if (!value) continue
|
||||
const cleaned = value.replace(YEAR_SUFFIX_RE, '')
|
||||
if (cleaned !== value) el.setAttribute(attr, cleaned)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Убирает «г.» из служебных подписей дней внутри сетки FullCalendar.
|
||||
*
|
||||
* Кроме подписи периода (её формат мы задаём сами, см. `buildTitleFormatter`),
|
||||
* дату дня FullCalendar подставляет ещё и в подпись ячейки — она видна в
|
||||
* accessibility-дереве и как всплывающая подсказка. Формат этой подписи
|
||||
* библиотека наружу НЕ отдаёт: `buildNavLinkAttrs` (@fullcalendar/core)
|
||||
* форматирует дату жёстко зашитым `{year,month:'long',day:'numeric'}` и кладёт
|
||||
* результат в `aria-label` (в `title` — когда включены navLinks), а с локалью
|
||||
* `ru` Intl всегда даёт «20 июля 2026 г.». Настройкой это не убирается,
|
||||
* поэтому правим уже отрендеренный атрибут.
|
||||
*
|
||||
* Почему MutationObserver, а не хуки `dayCellDidMount`/`dayHeaderDidMount`:
|
||||
* при переключении недельного вида на месячный FullCalendar ПЕРЕИСПОЛЬЗУЕТ
|
||||
* уже смонтированные ячейки — `didMount` для них повторно не зовётся, а
|
||||
* подпись внутри перерисовывается заново, и «г.» возвращается (поймали на
|
||||
* живой проверке: после переключения вида ровно строка переиспользованных
|
||||
* ячеек оставалась с «г.»). Наблюдатель же ловит любую перерисовку.
|
||||
* Зацикливания нет: `stripYearSuffixAttrs` пишет атрибут, только если он
|
||||
* реально меняется, поэтому вызванная нашей же правкой итерация ничего не
|
||||
* пишет и цепочка обрывается.
|
||||
*/
|
||||
function useStripYearSuffix(rootRef: React.RefObject<HTMLElement | null>) {
|
||||
useEffect(() => {
|
||||
const root = rootRef.current
|
||||
if (!root) return
|
||||
const clean = () => root.querySelectorAll(DAY_LABEL_SELECTOR).forEach(stripYearSuffixAttrs)
|
||||
clean()
|
||||
const observer = new MutationObserver(clean)
|
||||
observer.observe(root, { subtree: true, childList: true, attributeFilter: ['aria-label', 'title'] })
|
||||
return () => observer.disconnect()
|
||||
}, [rootRef])
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма аргумента, который FullCalendar передаёт функции-`titleFormat`
|
||||
* (`VerboseFormattingArg` из @fullcalendar/core/internal-common — не
|
||||
@@ -152,6 +203,9 @@ export function ConferenceCalendar({
|
||||
}: ConferenceCalendarProps) {
|
||||
const calendarRef = useRef<FullCalendar | null>(null)
|
||||
const titleRef = useRef<HTMLSpanElement | null>(null)
|
||||
const gridRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useStripYearSuffix(gridRef)
|
||||
|
||||
// См. комментарий у `latestOnEditClick` в начале файла — синхронизируем
|
||||
// переменную вместо того, чтобы замыкать пропс напрямую в renderEventContent.
|
||||
@@ -269,7 +323,7 @@ export function ConferenceCalendar({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="vc-calendar" aria-busy={isLoading}>
|
||||
<div className="vc-calendar" aria-busy={isLoading} ref={gridRef}>
|
||||
<FullCalendar
|
||||
ref={calendarRef}
|
||||
plugins={CALENDAR_PLUGINS}
|
||||
|
||||
Reference in New Issue
Block a user