@plastic-js/tsumiki 0.1.39 → 0.1.40

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.
@@ -3,10 +3,9 @@ import { insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/
3
3
  import { css } from "@emotion/css";
4
4
  import { createEffect, createSignal, mergeProps as mergeProps$1, splitProps } from "@plastic-js/plastic";
5
5
  //#region src/components/BottomSheet.jsx
6
- var _tmpl4 = template("<div><h2></h2></div>");
7
- var _tmpl3 = template("<div><div></div></div>");
8
- var _tmpl2 = template("<div role=\"dialog\" aria-modal=\"true\"><!><!><div></div><!></div>");
9
- var _tmpl = template("<div></div>");
6
+ var _tmpl3 = template("<div data-part=\"grabber\"><div></div></div>");
7
+ var _tmpl2 = template("<div role=\"dialog\" aria-modal=\"true\" aria-label=\"BottomSheet\"><!><div data-part=\"content\"></div></div>");
8
+ var _tmpl = template("<div data-part=\"backdrop\"></div>");
10
9
  var CLOSE_DISTANCE_RATIO = .3;
11
10
  var CLOSE_VELOCITY = .5;
12
11
  var RUBBER_BAND_FACTOR = 3;
@@ -37,6 +36,7 @@ var sheetClass = css({
37
36
  borderRadius: "var(--tsu-radius-l3-md) var(--tsu-radius-l3-md) 0 0",
38
37
  boxShadow: "var(--tsu-shadow-lg)",
39
38
  maxHeight: "70vh",
39
+ "@supports (height: 100dvh)": { maxHeight: "min(70dvh, 70vh)" },
40
40
  display: "flex",
41
41
  flexDirection: "column",
42
42
  transform: "translateY(100%)",
@@ -62,7 +62,7 @@ var grabberAreaClass = css({
62
62
  display: "flex",
63
63
  alignItems: "center",
64
64
  justifyContent: "center",
65
- padding: "6px 0",
65
+ height: "24px",
66
66
  flexShrink: 0,
67
67
  cursor: "grab",
68
68
  touchAction: "none",
@@ -74,35 +74,12 @@ var grabberClass = css({
74
74
  borderRadius: "2.5px",
75
75
  background: "var(--tsu-neutral-alpha-6)"
76
76
  });
77
- var headerClass = css({
77
+ var contentClass = css({
78
78
  display: "flex",
79
- alignItems: "center",
80
- justifyContent: "center",
81
- padding: "var(--tsu-container-padding-lg)",
82
- borderBottom: "1px solid var(--tsu-neutral-7)",
83
- flexShrink: 0
84
- });
85
- var titleClass = css({
86
- margin: 0,
87
- fontSize: "var(--tsu-text-title-size)",
88
- fontWeight: "var(--tsu-text-title-weight)",
89
- lineHeight: "var(--tsu-text-title-line-height)",
90
- color: "var(--tsu-fg)",
91
- textAlign: "center"
92
- });
93
- var bodyClass = css({
94
- overflowY: "auto",
95
- padding: "var(--tsu-container-padding-lg)",
79
+ flexDirection: "column",
80
+ padding: "0 var(--tsu-container-padding-lg)",
96
81
  flex: 1,
97
- WebkitOverflowScrolling: "touch"
98
- });
99
- var bodyNoHeaderClass = css({ paddingTop: "calc(var(--tsu-container-padding-lg) * 2)" });
100
- var footerClass = css({
101
- display: "flex",
102
- gap: "var(--tsu-spacing-sm)",
103
- padding: "0 var(--tsu-container-padding-lg) var(--tsu-container-padding-lg)",
104
- flexShrink: 0,
105
- paddingTop: "var(--tsu-spacing-sm)"
82
+ minHeight: 0
106
83
  });
107
84
  var read = (v) => typeof v === "function" ? v() : v;
108
85
  var BottomSheet = (props) => {
@@ -115,15 +92,12 @@ var BottomSheet = (props) => {
115
92
  "onOpen",
116
93
  "onClose",
117
94
  "onOpenChange",
118
- "title",
119
95
  "dismissible",
120
96
  "draggable",
121
97
  "backdropBlur",
122
98
  "backdropClassName",
123
99
  "backdropStyle",
124
- "bodyClassName",
125
- "footer",
126
- "footerClass",
100
+ "contentClassName",
127
101
  "class",
128
102
  "children"
129
103
  ]);
@@ -197,8 +171,6 @@ var BottomSheet = (props) => {
197
171
  const _el0 = _tmpl2.cloneNode(true);
198
172
  const _el1 = _el0.firstChild;
199
173
  const _el2 = _el0.firstChild.nextSibling;
200
- const _el3 = _el0.firstChild.nextSibling.nextSibling;
201
- const _el4 = _el0.firstChild.nextSibling.nextSibling.nextSibling;
202
174
  insert(_el0, () => showGrabber && (() => {
203
175
  const _el0 = _tmpl3.cloneNode(true);
204
176
  const _el1 = _el0.firstChild;
@@ -210,22 +182,8 @@ var BottomSheet = (props) => {
210
182
  setProp(_el0, "onPointerCancel", () => onGrabberPointerCancel);
211
183
  return _el0;
212
184
  })(), _el1);
213
- insert(_el0, () => local.title && (() => {
214
- const _el0 = _tmpl4.cloneNode(true);
215
- const _el1 = _el0.firstChild;
216
- insert(_el1, () => local.title);
217
- setProp(_el1, "className", () => titleClass);
218
- setProp(_el0, "className", () => headerClass);
219
- return _el0;
220
- }), _el2);
221
- insert(_el3, () => local.children);
222
- setProp(_el3, "className", () => [!local.title && !showGrabber ? `${bodyClass} ${bodyNoHeaderClass}` : bodyClass, local.bodyClassName].filter(Boolean).join(" "));
223
- insert(_el0, () => local.footer && (() => {
224
- const _el0 = _tmpl.cloneNode(true);
225
- insert(_el0, () => local.footer);
226
- setProp(_el0, "className", () => local.footerClass ? `${footerClass} ${local.footerClass}` : footerClass);
227
- return _el0;
228
- }), _el4);
185
+ insert(_el2, () => local.children);
186
+ setProp(_el2, "className", () => [contentClass, local.contentClassName].filter(Boolean).join(" "));
229
187
  setProp(_el0, "ref", (el) => {
230
188
  sheetEl = el;
231
189
  });
@@ -235,7 +193,6 @@ var BottomSheet = (props) => {
235
193
  local.class
236
194
  ].filter(Boolean).join(" "));
237
195
  setProp(_el0, "data-open", () => read(local.open) ? "" : void 0);
238
- setProp(_el0, "aria-label", () => local.title || "BottomSheet");
239
196
  return _el0;
240
197
  }] }));
241
198
  };
@@ -60,7 +60,8 @@ var contentModalClass = css({
60
60
  borderRadius: "var(--tsu-radius-l3-md)",
61
61
  width: "100%",
62
62
  maxWidth: "440px",
63
- maxHeight: "calc(100vh - var(--tsu-spacing-lg) * 2)",
63
+ maxHeight: "min(calc(100vh - var(--tsu-spacing-lg) * 2), 560px)",
64
+ "@supports (height: 100dvh)": { maxHeight: "min(calc(100dvh - var(--tsu-spacing-lg) * 2), 560px)" },
64
65
  display: "flex",
65
66
  flexDirection: "column",
66
67
  color: "var(--tsu-fg)",
@@ -82,7 +83,8 @@ var headerClass = css({
82
83
  display: "flex",
83
84
  alignItems: "center",
84
85
  justifyContent: "space-between",
85
- padding: "18px var(--tsu-container-padding-lg)",
86
+ padding: "18px var(--tsu-container-padding-lg) var(--tsu-spacing-lg)",
87
+ marginBottom: "var(--tsu-spacing-sm)",
86
88
  borderBottom: "1px solid var(--tsu-neutral-7)",
87
89
  flexShrink: 0
88
90
  });
@@ -117,16 +119,16 @@ var closeBtnClass = css({
117
119
  flexShrink: 0
118
120
  });
119
121
  var bodyClass = css({
120
- padding: "var(--tsu-container-padding-lg)",
122
+ padding: "0 var(--tsu-container-padding-lg)",
121
123
  flex: 1,
124
+ minHeight: 0,
122
125
  overflowY: "auto",
123
126
  WebkitOverflowScrolling: "touch"
124
127
  });
125
- var bodySheetNoHeaderClass = css({ paddingTop: "calc(var(--tsu-container-padding-lg) * 2)" });
126
128
  var footerClass = css({
127
129
  display: "flex",
128
130
  gap: "var(--tsu-spacing-md)",
129
- padding: "var(--tsu-spacing-lg) var(--tsu-container-padding-lg) var(--tsu-container-padding-lg)",
131
+ padding: "var(--tsu-container-padding-lg)",
130
132
  flexShrink: 0
131
133
  });
132
134
  var footerBtnClass = css({
@@ -167,6 +169,7 @@ var btnDangerClass = css({
167
169
  }
168
170
  });
169
171
  var read = (v) => typeof v === "function" ? v() : v;
172
+ var warnedSheetNoTitle = false;
170
173
  var Dialog$1 = (props) => {
171
174
  const [local, rest] = splitProps(mergeProps$2({
172
175
  mode: "sheet",
@@ -250,8 +253,10 @@ var Dialog$1 = (props) => {
250
253
  setProp(_el0, "className", () => onlyOneBtn ? `${footerBtnClass} tsu-dialog-footer--center` : footerBtnClass);
251
254
  return _el0;
252
255
  } : void 0;
253
- const hasFooter = !!footerContent;
254
- const hasHeader = local.title || !isSheet && (local.closeable ?? true);
256
+ if (isSheet && !local.title && !warnedSheetNoTitle) {
257
+ warnedSheetNoTitle = true;
258
+ console.warn("[Dialog] sheet mode requires a title; use BottomSheet for header-less content.");
259
+ }
255
260
  return jsx(Dialog.Root, mergeProps({
256
261
  get open() {
257
262
  return read(local.open);
@@ -280,7 +285,7 @@ var Dialog$1 = (props) => {
280
285
  return local.class ? `${isSheet ? contentSheetClass : contentModalClass} ${local.class}` : isSheet ? contentSheetClass : contentModalClass;
281
286
  },
282
287
  children: [
283
- () => hasHeader && (() => {
288
+ () => {
284
289
  const _el0 = _tmpl4.cloneNode(true);
285
290
  const _el1 = _el0.firstChild;
286
291
  const _el2 = _el1.firstChild;
@@ -308,19 +313,19 @@ var Dialog$1 = (props) => {
308
313
  })), _el4);
309
314
  setProp(_el0, "className", () => headerClass);
310
315
  return _el0;
311
- }),
316
+ },
312
317
  () => {
313
318
  const _el0 = _tmpl5.cloneNode(true);
314
319
  insert(_el0, () => local.children);
315
- setProp(_el0, "className", () => isSheet && !local.title && !(local.closeable ?? true) ? `${bodyClass} ${bodySheetNoHeaderClass}` : bodyClass);
320
+ setProp(_el0, "className", () => bodyClass);
316
321
  return _el0;
317
322
  },
318
- () => hasFooter && (() => {
323
+ () => {
319
324
  const _el0 = _tmpl6.cloneNode(true);
320
325
  insert(_el0, () => footerContent);
321
326
  setProp(_el0, "className", () => footerClass);
322
327
  return _el0;
323
- })
328
+ }
324
329
  ]
325
330
  }))
326
331
  }))] })) }));
@@ -59,7 +59,13 @@ var triggerIndicatorClass = css({
59
59
  }
60
60
  });
61
61
  var listBodyClass = css({ padding: 0 });
62
- var listClass = css({ padding: "0 8px calc(8px + env(safe-area-inset-bottom, 0px))" });
62
+ var listClass = css({
63
+ flex: 1,
64
+ minHeight: 0,
65
+ overflowY: "auto",
66
+ WebkitOverflowScrolling: "touch",
67
+ padding: "0 8px calc(8px + env(safe-area-inset-bottom, 0px))"
68
+ });
63
69
  var itemClass = css({
64
70
  width: "100%",
65
71
  display: "flex",
@@ -308,7 +314,7 @@ var Content = () => {
308
314
  get backdropStyle() {
309
315
  return ctx.backdropStyle ?? { background: "rgba(0,0,0,0.5)" };
310
316
  },
311
- bodyClassName: listBodyClass,
317
+ contentClassName: listBodyClass,
312
318
  children: jsx("div", mergeProps(() => part("list"), {
313
319
  className: listClass,
314
320
  children: [() => ctx.filter && (() => {
package/docs/api.md CHANGED
@@ -641,6 +641,8 @@ Passthrough: Ark `Root` props (`value`, `min`, `max`, …).
641
641
 
642
642
  **Rendered DOM parts:** content is `[data-scope="dialog"][data-part="content"]`; tsumiki adds `data-part="header"`, `data-part="body"`, `data-part="footer"`, and the action buttons `data-part="negative-trigger"` / `data-part="positive-trigger"`. `title`, `description`, and `closeTrigger` carry `data-part` from Ark.
643
643
 
644
+ > **Note:** Dialog never renders a grabber / drag handle in either mode (`modal` or `sheet`). Drag-to-dismiss and the grabber are exclusive to [`BottomSheet`](#bottomsheet) — this is a deliberate difference between the two components.
645
+
644
646
  | Prop | Type | Default | Description |
645
647
  |---|---|---|---|
646
648
  | `mode` | `string` | `'sheet'` | `'modal'` (centered) or `'sheet'` (bottom sheet). |
@@ -747,16 +749,26 @@ Passthrough: Ark `Root` props (`open`, `onOpenChange`, `positioning`, …).
747
749
 
748
750
  ### BottomSheet
749
751
 
750
- Mobile bottom sheet (drag-to-dismiss).
752
+ A free-form presentation layer: a backdrop, an optional drag grabber, and a scrollable content area (`children`). The content is fully consumer-owned — headers, titles, and footers are just regular content.
751
753
 
752
754
  **Exports:** `BottomSheet` · no `.origin`
753
755
 
756
+ **Rendered DOM parts:** exactly three — `data-part="backdrop"`, `data-part="grabber"` (only when `draggable`), and `data-part="content"`. BottomSheet has no focus trap or Escape-to-close — use [`Dialog`](#dialog) when those are required.
757
+
758
+ > **Design decision:** `content` is a pure container — `display: flex; flex-direction: column` with `padding: 0 24px` only. It has **no vertical padding, no margin, and no scrolling**. Spacing and scrolling are entirely consumer-owned: build your own layout inside `children` (the sheet caps height at `70vh`, so a scrollable section can be a `flex: 1; min-height: 0; overflow-y: auto` child).
759
+
754
760
  | Prop | Type | Default | Description |
755
761
  |---|---|---|---|
756
- | `dismissible` | `boolean` | `true` | Allow dismiss. |
762
+ | `open` | `boolean \| (() => boolean)` | | **Required** controlled open state. |
763
+ | `onOpenChange` | `({ open }) => void` | — | Fired on open/close; sync your state here. |
764
+ | `dismissible` | `boolean` | `true` | Allow dismiss via backdrop / drag. |
765
+ | `draggable` | `boolean` | `true` | Show the grabber and enable drag-to-dismiss. |
757
766
  | `backdropBlur` | `boolean` | `true` | Blur the backdrop. |
758
-
759
- Passthrough: DOM props (`open`, `onOpenChange`, `title`, `children`, …).
767
+ | `backdropClassName` | `string` | — | Extra class for the backdrop. |
768
+ | `backdropStyle` | `object` | | Extra style for the backdrop. |
769
+ | `contentClassName` | `string` | — | Extra class for the content area. |
770
+ | `class` | `string` | — | Extra class for the sheet. |
771
+ | `children` | `node` | — | **Required** body content. |
760
772
 
761
773
  ---
762
774
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plastic-js/tsumiki",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "A UI component library for Plastic JS.",
5
5
  "license": "MIT",
6
6
  "author": "tigre",