Show the seating editor as a centered popup
Add Fluid.Popup, a centered dialog with the same open/close API as Sheet: it scales in like the confirm alert, fades out in place, and closes on backdrop tap or Escape with focus trapped inside. The host's seating editor now uses it instead of a bottom sheet; its list scrolls inside the popup on short screens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dd39c043c4
commit
ed3d1e437e
4 files changed
+132
-13
No files matched your search
@@ -4,6 +4,7 @@
|
||||
// 画面上のいまの位置と速度から続くので、動いている最中につかんだり戻したりできる
|
||||
// - VelocityTracker / project / rubberband: 指の速度・勢いから止まる位置の先読み・端での抵抗
|
||||
// - Sheet: 下から出るシート(つまみを下へ払うと閉じる)
|
||||
// - Popup: 画面の中央に出るポップアップ(背景を押す・Esc で閉じる)
|
||||
// - Segmented: つまみが滑るセグメント(つまみをドラッグしても選べる)
|
||||
// - presence / countTo / shake / haptic / navBar: 出し入れ・数字の変化・揺れ・振動・ナビゲーションバー
|
||||
// CSP でインラインスタイルを禁止しているので、動きはすべて CSSOM(el.style)で付ける
|
||||
@@ -304,6 +305,86 @@
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- ポップアップ ----------
|
||||
// root は背景(.popup-backdrop)、中に .popup。画面の中央に少し大きい所から縮んで現れ、閉じるときはその場で薄くなる
|
||||
// (App.confirm のアラートと同じ動き)。背景を押す・Esc で閉じる。
|
||||
// opts: { focus(開いたときにフォーカスする要素のセレクタ), onClose, onClosed }
|
||||
function Popup(root, opts = {}) {
|
||||
const box = root.querySelector('.popup');
|
||||
const appear = new Spring(0, { precision: 0.002 });
|
||||
let isOpen = false;
|
||||
let closing = false;
|
||||
let prevFocus = null;
|
||||
let downOnBackdrop = false;
|
||||
|
||||
appear.onUpdate = (v) => {
|
||||
root.style.setProperty('--appear', v.toFixed(3));
|
||||
box.style.opacity = v >= 1 ? '' : v.toFixed(3);
|
||||
box.style.transform = closing || v >= 1 || reducedMotion() ? '' : `scale(${(1.08 - 0.08 * v).toFixed(4)})`;
|
||||
};
|
||||
|
||||
function onKey(e) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
close();
|
||||
} else if (e.key === 'Tab') {
|
||||
trapFocus(box, e);
|
||||
}
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (isOpen) return;
|
||||
isOpen = true;
|
||||
closing = false;
|
||||
if (root.hidden) {
|
||||
root.hidden = false;
|
||||
appear.set(0);
|
||||
}
|
||||
root.style.pointerEvents = '';
|
||||
appear.to(1, { damping: 1, response: 0.32, allowReduced: true });
|
||||
prevFocus = document.activeElement;
|
||||
document.addEventListener('keydown', onKey);
|
||||
const target = opts.focus ? box.querySelector(opts.focus) : null;
|
||||
(target || box).focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!isOpen) return;
|
||||
isOpen = false;
|
||||
closing = true;
|
||||
document.removeEventListener('keydown', onKey);
|
||||
root.style.pointerEvents = 'none'; // 消えている途中の背景で次の操作を止めない
|
||||
appear.to(0, {
|
||||
damping: 1,
|
||||
response: 0.2,
|
||||
allowReduced: true,
|
||||
onRest: () => {
|
||||
if (isOpen) return;
|
||||
root.hidden = true;
|
||||
if (opts.onClosed) opts.onClosed();
|
||||
},
|
||||
});
|
||||
if (prevFocus && prevFocus.focus && document.contains(prevFocus)) prevFocus.focus({ preventScroll: true });
|
||||
if (opts.onClose) opts.onClose();
|
||||
}
|
||||
|
||||
// 背景を押したら閉じる(ポップアップの中から背景へのドラッグは除く)
|
||||
root.addEventListener('pointerdown', (e) => {
|
||||
downOnBackdrop = e.target === root;
|
||||
});
|
||||
root.addEventListener('click', (e) => {
|
||||
if (e.target === root && downOnBackdrop) close();
|
||||
});
|
||||
|
||||
return {
|
||||
open,
|
||||
close,
|
||||
get isOpen() {
|
||||
return isOpen;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- セグメント ----------
|
||||
// container の子(label > input[type=radio] か、.active の付いた button)を選ぶつまみを滑らせる。
|
||||
// 選ばれている所を押すとつまみが少し縮み、そのまま横へドラッグすると指に付いてくる。
|
||||
@@ -548,6 +629,7 @@
|
||||
clamp,
|
||||
reducedMotion,
|
||||
Sheet,
|
||||
Popup,
|
||||
Segmented,
|
||||
presence,
|
||||
countTo,
|
||||
|
||||
Reference in new issue
Block a user