release: v0.19.0

Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
Ralksta
2026-09-29 22:50:30 +02:00
committed by GitHub
parent 5f251923f0
commit ad8b09700b
297 changed files with 19398 additions and 1800 deletions

View File

@@ -27,8 +27,22 @@ import { useEffect, useRef } from 'react';
* have to call the hook — hooks may not run conditionally. Without it the
* effect would run once against an unmounted card, bail out, and never run
* again, because `onClose` alone does not change when the modal opens.
*
* Dialogs nest — the album drawer opens the asset picker, the backup manager
* a confirmation. Every open dialog listens on `document`, so only the one
* opened last handles a key; otherwise one Escape closed the whole stack.
*
* Escape is also left alone while the focused element owns it: an expanded
* combobox (the admin Listbox closes its popup) or a dnd-kit item picked up
* with the keyboard (Escape cancels the move). The listener runs in the
* capture phase and stops propagation, so neither would see the key.
*/
/** Open dialogs, innermost last. */
const openDialogs: object[] = [];
const ESCAPE_OWNERS = '[aria-expanded="true"], [aria-roledescription][aria-pressed="true"]';
const FOCUSABLE = [
'a[href]',
'button:not([disabled])',
@@ -67,6 +81,8 @@ export function useModalDialog(onClose: () => void, active = true) {
if (!active || !card) return;
const previous = document.activeElement as HTMLElement | null;
const token = {};
openDialogs.push(token);
// React has already applied `autoFocus` by the time the effect runs, so
// an existing focus inside the card is respected rather than overridden.
@@ -75,7 +91,10 @@ export function useModalDialog(onClose: () => void, active = true) {
}
function onKeyDown(e: KeyboardEvent) {
if (openDialogs[openDialogs.length - 1] !== token) return;
if (e.key === 'Escape') {
const active = document.activeElement;
if (active && card?.contains(active) && active.matches(ESCAPE_OWNERS)) return;
e.stopPropagation();
onCloseRef.current();
return;
@@ -105,6 +124,7 @@ export function useModalDialog(onClose: () => void, active = true) {
document.addEventListener('keydown', onKeyDown, true);
return () => {
document.removeEventListener('keydown', onKeyDown, true);
openDialogs.splice(openDialogs.indexOf(token), 1);
// Only take the focus back if it is still inside the closing dialog.
// Something else may legitimately have claimed it in the meantime.
if (previous && document.contains(previous)) previous.focus();