@plastic-js/tsumiki 0.1.63 → 0.1.65
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Portal_default from "./Portal.js";
|
|
2
|
-
import { insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
2
|
+
import { Fragment, insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
3
3
|
import { css, keyframes } from "@emotion/css";
|
|
4
4
|
import { Either, createEffect, createSignal, mergeProps as mergeProps$1, onCleanup, splitProps } from "@plastic-js/plastic";
|
|
5
5
|
//#region src/components/BottomSheet.jsx
|
|
@@ -102,7 +102,8 @@ var BottomSheet = (props) => {
|
|
|
102
102
|
backdropBlur: true,
|
|
103
103
|
draggable: true,
|
|
104
104
|
lazyMount: true,
|
|
105
|
-
unmountOnExit: true
|
|
105
|
+
unmountOnExit: true,
|
|
106
|
+
portal: true
|
|
106
107
|
}, props), [
|
|
107
108
|
"open",
|
|
108
109
|
"onOpen",
|
|
@@ -116,6 +117,7 @@ var BottomSheet = (props) => {
|
|
|
116
117
|
"contentClassName",
|
|
117
118
|
"lazyMount",
|
|
118
119
|
"unmountOnExit",
|
|
120
|
+
"portal",
|
|
119
121
|
"class",
|
|
120
122
|
"children"
|
|
121
123
|
]);
|
|
@@ -193,47 +195,48 @@ var BottomSheet = (props) => {
|
|
|
193
195
|
settle(false);
|
|
194
196
|
};
|
|
195
197
|
const showGrabber = read(local.draggable);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
198
|
+
const renderOverlay = () => jsx(Fragment, { children: [(() => {
|
|
199
|
+
const _el0 = _tmpl.cloneNode(true);
|
|
200
|
+
setProp(_el0, "className", () => [
|
|
201
|
+
backdropClass,
|
|
202
|
+
local.backdropBlur && backdropBlurClass,
|
|
203
|
+
local.backdropClassName
|
|
204
|
+
].filter(Boolean).join(" "));
|
|
205
|
+
setProp(_el0, "style", () => local.backdropStyle);
|
|
206
|
+
setProp(_el0, "data-open", () => read(local.open) ? "" : void 0);
|
|
207
|
+
setProp(_el0, "onClick", () => handleBackdropClick);
|
|
208
|
+
return _el0;
|
|
209
|
+
})(), () => {
|
|
210
|
+
const _el0 = _tmpl2.cloneNode(true);
|
|
211
|
+
const _el1 = _el0.firstChild;
|
|
212
|
+
const _el2 = _el0.firstChild.nextSibling;
|
|
213
|
+
insert(_el0, () => showGrabber && (() => {
|
|
214
|
+
const _el0 = _tmpl3.cloneNode(true);
|
|
211
215
|
const _el1 = _el0.firstChild;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
setProp(_el0, "onPointerDown", () => onGrabberPointerDown);
|
|
219
|
-
setProp(_el0, "onPointerMove", () => onGrabberPointerMove);
|
|
220
|
-
setProp(_el0, "onPointerUp", () => onGrabberPointerUp);
|
|
221
|
-
setProp(_el0, "onPointerCancel", () => onGrabberPointerCancel);
|
|
222
|
-
return _el0;
|
|
223
|
-
})(), _el1);
|
|
224
|
-
insert(_el2, () => local.children);
|
|
225
|
-
setProp(_el2, "className", () => [contentClass, local.contentClassName].filter(Boolean).join(" "));
|
|
226
|
-
setProp(_el0, "ref", (el) => {
|
|
227
|
-
sheetEl = el;
|
|
228
|
-
});
|
|
229
|
-
setProp(_el0, "className", () => [
|
|
230
|
-
sheetClass,
|
|
231
|
-
dragging() && draggingClass,
|
|
232
|
-
local.class
|
|
233
|
-
].filter(Boolean).join(" "));
|
|
234
|
-
setProp(_el0, "data-open", () => read(local.open) ? "" : void 0);
|
|
216
|
+
setProp(_el1, "className", () => grabberClass);
|
|
217
|
+
setProp(_el0, "className", () => grabberAreaClass);
|
|
218
|
+
setProp(_el0, "onPointerDown", () => onGrabberPointerDown);
|
|
219
|
+
setProp(_el0, "onPointerMove", () => onGrabberPointerMove);
|
|
220
|
+
setProp(_el0, "onPointerUp", () => onGrabberPointerUp);
|
|
221
|
+
setProp(_el0, "onPointerCancel", () => onGrabberPointerCancel);
|
|
235
222
|
return _el0;
|
|
236
|
-
}
|
|
223
|
+
})(), _el1);
|
|
224
|
+
insert(_el2, () => local.children);
|
|
225
|
+
setProp(_el2, "className", () => [contentClass, local.contentClassName].filter(Boolean).join(" "));
|
|
226
|
+
setProp(_el0, "ref", (el) => {
|
|
227
|
+
sheetEl = el;
|
|
228
|
+
});
|
|
229
|
+
setProp(_el0, "className", () => [
|
|
230
|
+
sheetClass,
|
|
231
|
+
dragging() && draggingClass,
|
|
232
|
+
local.class
|
|
233
|
+
].filter(Boolean).join(" "));
|
|
234
|
+
setProp(_el0, "data-open", () => read(local.open) ? "" : void 0);
|
|
235
|
+
return _el0;
|
|
236
|
+
}] });
|
|
237
|
+
return jsx(Either, mergeProps({
|
|
238
|
+
condition: () => render(),
|
|
239
|
+
trueBranch: () => read(local.portal) ? jsx(Portal_default, mergeProps({ children: () => renderOverlay() })) : renderOverlay(),
|
|
237
240
|
falseBranch: () => null
|
|
238
241
|
}));
|
|
239
242
|
};
|
|
@@ -2,7 +2,8 @@ import BottomSheet from "./BottomSheet.js";
|
|
|
2
2
|
import Icon from "./Icon.js";
|
|
3
3
|
import { Fragment, insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
4
4
|
import { css } from "@emotion/css";
|
|
5
|
-
import {
|
|
5
|
+
import { useDialogContext } from "@plastic-js/ark";
|
|
6
|
+
import { Loop, createContext, createEffect, createSignal, mergeProps as mergeProps$2, splitProps, useContext } from "@plastic-js/plastic";
|
|
6
7
|
//#region src/components/Select.jsx
|
|
7
8
|
var _tmpl3 = template("<div>No results</div>");
|
|
8
9
|
var _tmpl2 = template("<button type=\"button\" aria-label=\"Clear search\"></button>");
|
|
@@ -209,8 +210,8 @@ var useSelect = () => {
|
|
|
209
210
|
var read = (source) => {
|
|
210
211
|
return typeof source === "function" ? source() : source;
|
|
211
212
|
};
|
|
212
|
-
var Select = (props) => {
|
|
213
|
-
const [local, rest] = splitProps(mergeProps$
|
|
213
|
+
var Select$1 = (props) => {
|
|
214
|
+
const [local, rest] = splitProps(mergeProps$2({
|
|
214
215
|
size: "md",
|
|
215
216
|
variant: "outline",
|
|
216
217
|
intent: "default",
|
|
@@ -360,13 +361,8 @@ var Item = (props) => {
|
|
|
360
361
|
};
|
|
361
362
|
var List = () => {
|
|
362
363
|
const ctx = useSelect();
|
|
363
|
-
let filterInputEl = null;
|
|
364
|
-
createEffect(() => {
|
|
365
|
-
if (ctx.open() && ctx.filter) queueMicrotask(() => filterInputEl?.focus());
|
|
366
|
-
});
|
|
367
364
|
const handleFilterClear = () => {
|
|
368
365
|
ctx.query("");
|
|
369
|
-
filterInputEl?.focus();
|
|
370
366
|
};
|
|
371
367
|
return jsx("div", mergeProps(() => part("list"), {
|
|
372
368
|
className: listClass,
|
|
@@ -377,9 +373,6 @@ var List = () => {
|
|
|
377
373
|
const _el3 = _el0.firstChild.nextSibling.nextSibling;
|
|
378
374
|
insert(_el1, () => jsx(Icon, mergeProps({ svg: searchSvg })));
|
|
379
375
|
setProp(_el1, "className", () => filterIconClass);
|
|
380
|
-
setProp(_el2, "ref", (el) => {
|
|
381
|
-
filterInputEl = el;
|
|
382
|
-
});
|
|
383
376
|
setProp(_el2, "className", () => `${filterInputClass} ${intentClasses[ctx.intent()] ?? intentClasses.default} ${ctx.invalid() ? errorClass : ""}`);
|
|
384
377
|
setProp(_el2, "value", () => ctx.query);
|
|
385
378
|
setProp(_el2, "onInput", (e) => ctx.query(e.target.value));
|
|
@@ -430,8 +423,9 @@ var Content = () => {
|
|
|
430
423
|
return ctx.backdropStyle ?? { background: "var(--tsu-bg-overlay)" };
|
|
431
424
|
},
|
|
432
425
|
contentClassName: listBodyClass,
|
|
426
|
+
portal: !useDialogContext(),
|
|
433
427
|
children: jsx(List, {})
|
|
434
428
|
}));
|
|
435
429
|
};
|
|
436
430
|
//#endregion
|
|
437
|
-
export { SelectTrigger, Select as default };
|
|
431
|
+
export { SelectTrigger, Select$1 as default };
|
package/dist/components/Tabs.js
CHANGED
|
@@ -22,6 +22,8 @@ var triggerClass = css({
|
|
|
22
22
|
fontSize: "var(--tsu-comp-font-size-md)",
|
|
23
23
|
fontFamily: "inherit",
|
|
24
24
|
cursor: "pointer",
|
|
25
|
+
userSelect: "none",
|
|
26
|
+
WebkitUserSelect: "none",
|
|
25
27
|
whiteSpace: "nowrap",
|
|
26
28
|
"&[data-selected]": { color: "var(--tsu-accent-subtle-fg)" },
|
|
27
29
|
"&[data-disabled]": {
|
package/docs/api.md
CHANGED
|
@@ -729,6 +729,7 @@ A free-form presentation layer: a backdrop, an optional drag grabber, and a scro
|
|
|
729
729
|
| `backdropClassName` | `string` | — | Extra class for the backdrop. |
|
|
730
730
|
| `backdropStyle` | `object` | — | Extra style for the backdrop. |
|
|
731
731
|
| `contentClassName` | `string` | — | Extra class for the content area. |
|
|
732
|
+
| `portal` | `boolean` | `true` | Render the overlay into `<body>`. When `false` the backdrop + sheet render **inline**, keeping them inside a parent Dialog's focus-trap boundary — required for a filter `input` nested in a Dialog on iOS Safari (WebKit focus-lock). The overlay is `position: fixed`, so it still covers the viewport when inline. |
|
|
732
733
|
| `class` | `string` | — | Extra class for the sheet. |
|
|
733
734
|
| `children` | `node` | — | **Required** body content. |
|
|
734
735
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Select-in-Dialog filter input focus-lock on iOS
|
|
2
|
+
|
|
3
|
+
**Status:** Fixed
|
|
4
|
+
**Affects:** `Select.jsx` (nested inside `Dialog`)
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
A `Select` with a filter input, nested inside a `Dialog`, could not be focused on
|
|
9
|
+
iOS Safari — tapping the search box did nothing and the keyboard never appeared.
|
|
10
|
+
|
|
11
|
+
The `Dialog` (ArkDialog `modal`) installs a focus trap that listens on
|
|
12
|
+
`document` and forces focus back into the dialog content whenever it escapes.
|
|
13
|
+
The Select's bottom sheet was portaled to `document.body`, which is **outside**
|
|
14
|
+
the dialog's focus-trap boundary. On iOS Safari the trap's `focusin` handler
|
|
15
|
+
stole focus back from the filter input on every tap, so the input never kept
|
|
16
|
+
focus long enough for the keyboard to appear.
|
|
17
|
+
|
|
18
|
+
The old `SelectMobile` component hit the exact same WebKit focus-lock bug and
|
|
19
|
+
fixed it by rendering the sheet **inline** (no `<Portal>`), keeping it inside
|
|
20
|
+
the dialog's focus-trap boundary. The current `Select` re-introduced the bug by
|
|
21
|
+
routing through `BottomSheet`, which always portaled.
|
|
22
|
+
|
|
23
|
+
## Fix
|
|
24
|
+
|
|
25
|
+
- `BottomSheet` gains a `portal` prop (default `true`, backward compatible).
|
|
26
|
+
When `false`, the backdrop + sheet render inline instead of into `<body>`.
|
|
27
|
+
- `Select.Content` reads `useDialogContext()` and passes `portal={!dialog}`:
|
|
28
|
+
inside a Dialog the sheet stays within the focus-trap boundary; standalone
|
|
29
|
+
usage still portals as before.
|
|
30
|
+
|
|
31
|
+
Because `BottomSheet`'s overlay is `position: fixed`, rendering inline still
|
|
32
|
+
covers the viewport correctly even when nested inside the dialog.
|
|
33
|
+
|
|
34
|
+
## e2e
|
|
35
|
+
|
|
36
|
+
`e2e/select-in-dialog-*` verifies the sheet stays inside the dialog content,
|
|
37
|
+
the filter input keeps focus after a tap, and closing the sheet via backdrop
|
|
38
|
+
leaves the dialog open.
|