@open-webapp/calendar 0.8.2 → 0.9.1
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.
- package/README.md +7 -1
- package/dist/react/CalendarPane.js +50 -1
- package/dist/styles.css +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ Timed meeting headers keep the time left and `[Join] [RSVP glyph]` right, with a
|
|
|
205
205
|
|
|
206
206
|
Both components follow the OS/browser `prefers-color-scheme` preference live in `'system'` mode; omitting `theme` preserves this default. Explicit `'light'` and `'dark'` ignore OS changes. Hosts can change the prop on a mounted component (including back to `'system'` or `undefined`) without remounting, closing an open dropdown, losing its search query, focus or keyboard-active option, changing selected calendars, or resetting the pane's current RSVP selection. Theme changes do not trigger selection callbacks or controller refreshes.
|
|
207
207
|
|
|
208
|
-
Each pane forwards its requested mode to its unified grouped footer selector, including both account groups, account-local actions and the shared search. The single-trigger structure and cross-account keyboard navigation are preserved; the approved trigger geometry
|
|
208
|
+
Each pane forwards its requested mode to its unified grouped footer selector, including both account groups, account-local actions and the shared search. The single-trigger structure and cross-account keyboard navigation are preserved; the approved trigger geometry and action-only styling exceptions are described here. Standalone selectors choose their own mode: an omitted/system selector follows the OS even inside an explicitly dark or light pane. Adjacent and nested instances can use opposite modes independently; no document/body theme switch is applied.
|
|
209
209
|
|
|
210
210
|
The host owns theme-picker UI and preference persistence (for example, saving a choice and passing it back as `theme`). The package neither stores the choice nor adds it to the controller. Hosts also own styling of `emptyState`, `sourceNotice` and `renderEventAction` slot content; give custom content explicit styles where needed rather than relying on the built-in palette.
|
|
211
211
|
|
|
@@ -225,6 +225,12 @@ The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark
|
|
|
225
225
|
|
|
226
226
|
The shared `.calendar-selector-trigger` uses border-box inline-flex, centered alignment, an 8px gap, 36px minimum height, 8px 12px padding, 6px radius, a 1px solid control border, inherited font and pointer cursor. `.calendar-selector-trigger-label` bounds long labels with ellipsis; `.calendar-selector-chevron` stays 12px and does not shrink or add a tab stop. Existing scoped surface/text/control-border tokens style the resting trigger, the hover token supplies its hover background, and the focus token supplies a 2px `:focus-visible` outline with 2px offset. This approved visual-only exception changes trigger geometry and its derived dropdown position, preserving surrounding layout and theme isolation. Import the shipped stylesheet for both footer and standalone use.
|
|
227
227
|
|
|
228
|
+
Done, Select all and Clear use the approved action-only flat styling via `.calendar-selector-actions > button` in standalone and grouped footer dropdowns: transparent resting background, zero border, no shadow, inherited font, `--calendar-selector-text` color and pointer cursor. Hover uses `--calendar-hover`; pointer focus has no decorative outline, while keyboard `:focus-visible` uses a 2px solid `--calendar-focus` outline with 2px offset. Action padding, row flex layout and 8px gap/top/bottom margins, footer/dropdown geometry, trigger/search/option styling, other native controls and all selection, callback and focus-return contracts are preserved.
|
|
229
|
+
|
|
230
|
+
## 0.9.0 (BREAKING pre-1.0 minor)
|
|
231
|
+
|
|
232
|
+
- User-approved replacement of the frozen native dropdown-action appearance: Done, Select all and Clear are flat and themed with hover and keyboard-focus treatment as described above. Hosts relying on the former native action appearance must adapt. Other visual, interaction, public API and storage contracts are preserved; no data migration is required.
|
|
233
|
+
|
|
228
234
|
## 0.8.0 (BREAKING pre-1.0 minor)
|
|
229
235
|
|
|
230
236
|
- Unaccepted independently toggles `needsAction`, replacing the exclusive shortcut and local restoration introduced in 0.3.0. Hosts relying on pending-only callbacks or restoration must adapt to callbacks reflecting current membership.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, formatRibbonLabel, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
|
|
4
4
|
import { GroupedCalendarSelector } from './CalendarSelector.js';
|
|
5
5
|
function toggleRsvpStatus(selection, status) {
|
|
@@ -144,6 +144,8 @@ export function CalendarPane(props) {
|
|
|
144
144
|
const chipsRef = useRef(null);
|
|
145
145
|
const scrollRef = useRef(null);
|
|
146
146
|
const popupRef = useRef(null);
|
|
147
|
+
const anchorSnapshotRef = useRef(null);
|
|
148
|
+
const didMountRsvpRef = useRef(false);
|
|
147
149
|
const [now, setNow] = useState(() => new Date());
|
|
148
150
|
const unacceptedCount = useMemo(() => events.filter((event) => event.selfResponseStatus === 'needsAction').length, [events]);
|
|
149
151
|
const filteredEvents = useMemo(() => filterByRsvp(events, selectedRsvpStatuses), [events, selectedRsvpStatuses]);
|
|
@@ -167,6 +169,53 @@ export function CalendarPane(props) {
|
|
|
167
169
|
const nextUpcomingEvent = findNextUpcomingEvent(filteredEvents, now);
|
|
168
170
|
const selectedEventKey = currentEventKeys.values().next().value ??
|
|
169
171
|
(nextUpcomingEvent ? getCalendarEventKey(nextUpcomingEvent) : undefined);
|
|
172
|
+
function captureAnchorSnapshot() {
|
|
173
|
+
const scrollEl = scrollRef.current;
|
|
174
|
+
if (!scrollEl)
|
|
175
|
+
return;
|
|
176
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
177
|
+
const offsets = new Map();
|
|
178
|
+
let anchor;
|
|
179
|
+
for (const [dateKey, header] of dayHeaderRefs.current) {
|
|
180
|
+
const rect = header.getBoundingClientRect();
|
|
181
|
+
offsets.set(dateKey, rect.top - scrollRect.top);
|
|
182
|
+
if (rect.bottom > scrollRect.top && (anchor === undefined || dateKey < anchor))
|
|
183
|
+
anchor = dateKey;
|
|
184
|
+
}
|
|
185
|
+
anchorSnapshotRef.current = anchor === undefined ? null : { anchor, offsets };
|
|
186
|
+
}
|
|
187
|
+
// Keep the visible date stable when RSVP filtering adds/removes day groups above the viewport.
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const scrollEl = scrollRef.current;
|
|
190
|
+
if (!scrollEl)
|
|
191
|
+
return;
|
|
192
|
+
const onScroll = () => captureAnchorSnapshot();
|
|
193
|
+
scrollEl.addEventListener('scroll', onScroll, { passive: true });
|
|
194
|
+
return () => scrollEl.removeEventListener('scroll', onScroll);
|
|
195
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
+
}, []);
|
|
197
|
+
useLayoutEffect(() => {
|
|
198
|
+
if (!didMountRsvpRef.current) {
|
|
199
|
+
didMountRsvpRef.current = true;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const scrollEl = scrollRef.current;
|
|
203
|
+
const snap = anchorSnapshotRef.current;
|
|
204
|
+
if (!scrollEl || !snap)
|
|
205
|
+
return;
|
|
206
|
+
const keys = Array.from(dayHeaderRefs.current.keys()).sort();
|
|
207
|
+
const target = (dayHeaderRefs.current.has(snap.anchor) ? snap.anchor : undefined) ??
|
|
208
|
+
keys.find((k) => k > snap.anchor && snap.offsets.has(k)) ??
|
|
209
|
+
keys.reverse().find((k) => k < snap.anchor && snap.offsets.has(k));
|
|
210
|
+
const header = target ? dayHeaderRefs.current.get(target) : undefined;
|
|
211
|
+
if (target && header) {
|
|
212
|
+
const delta = header.getBoundingClientRect().top - scrollEl.getBoundingClientRect().top - snap.offsets.get(target);
|
|
213
|
+
if (delta !== 0)
|
|
214
|
+
scrollEl.scrollTop += delta;
|
|
215
|
+
}
|
|
216
|
+
captureAnchorSnapshot();
|
|
217
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
218
|
+
}, [selectedRsvpStatuses]);
|
|
170
219
|
function handleToggle(status) {
|
|
171
220
|
props.onRsvpStatusesChange(toggleRsvpStatus(selectedRsvpStatuses, status));
|
|
172
221
|
}
|
package/dist/styles.css
CHANGED
|
@@ -729,6 +729,28 @@
|
|
|
729
729
|
color: var(--calendar-control-text);
|
|
730
730
|
}
|
|
731
731
|
|
|
732
|
+
.calendar-selector-actions > button {
|
|
733
|
+
border: 0;
|
|
734
|
+
background: transparent;
|
|
735
|
+
box-shadow: none;
|
|
736
|
+
color: var(--calendar-selector-text);
|
|
737
|
+
font: inherit;
|
|
738
|
+
cursor: pointer;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.calendar-selector-actions > button:hover {
|
|
742
|
+
background: var(--calendar-hover);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.calendar-selector-actions > button:focus {
|
|
746
|
+
outline: none;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.calendar-selector-actions > button:focus-visible {
|
|
750
|
+
outline: 2px solid var(--calendar-focus);
|
|
751
|
+
outline-offset: 2px;
|
|
752
|
+
}
|
|
753
|
+
|
|
732
754
|
.calendar-selector-checkbox {
|
|
733
755
|
flex: none;
|
|
734
756
|
margin: 0;
|