@plastic-js/tsumiki 0.1.64 → 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
- return jsx(Either, mergeProps({
197
- condition: () => render(),
198
- trueBranch: () => jsx(Portal_default, mergeProps({ 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);
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
- const _el2 = _el0.firstChild.nextSibling;
213
- insert(_el0, () => showGrabber && (() => {
214
- const _el0 = _tmpl3.cloneNode(true);
215
- const _el1 = _el0.firstChild;
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);
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 { Loop, createContext, createEffect, createSignal, mergeProps as mergeProps$1, splitProps, useContext } from "@plastic-js/plastic";
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$1({
213
+ var Select$1 = (props) => {
214
+ const [local, rest] = splitProps(mergeProps$2({
214
215
  size: "md",
215
216
  variant: "outline",
216
217
  intent: "default",
@@ -422,8 +423,9 @@ var Content = () => {
422
423
  return ctx.backdropStyle ?? { background: "var(--tsu-bg-overlay)" };
423
424
  },
424
425
  contentClassName: listBodyClass,
426
+ portal: !useDialogContext(),
425
427
  children: jsx(List, {})
426
428
  }));
427
429
  };
428
430
  //#endregion
429
- export { SelectTrigger, Select as default };
431
+ export { SelectTrigger, Select$1 as default };
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plastic-js/tsumiki",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "description": "A UI component library for Plastic JS.",
5
5
  "license": "MIT",
6
6
  "author": "tigre",