@plastic-js/tsumiki 0.1.70 → 0.1.72

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.
@@ -44,7 +44,7 @@ var iconClass = css({
44
44
  height: "22px"
45
45
  });
46
46
  var BackToTop = (props = {}) => {
47
- const [local] = splitProps(mergeProps$1({ threshold: 200 }, props), [
47
+ const [local] = splitProps(mergeProps$1({ threshold: 350 }, props), [
48
48
  "className",
49
49
  "threshold",
50
50
  "target",
@@ -122,7 +122,7 @@ var bodyClass = css({
122
122
  flex: 1,
123
123
  minHeight: 0,
124
124
  overflowY: "auto",
125
- WebkitOverflowScrolling: "touch"
125
+ overscrollBehavior: "contain"
126
126
  });
127
127
  var footerClass = css({
128
128
  display: "flex",
@@ -0,0 +1,224 @@
1
+ import { jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
2
+ import { css } from "@emotion/css";
3
+ import { Loop, mergeProps as mergeProps$1, splitProps } from "@plastic-js/plastic";
4
+ //#region src/components/PinInput.jsx
5
+ var _tmpl = template("<input maxLength=\"1\">");
6
+ var read = (v) => typeof v === "function" ? v() : v;
7
+ var rootClass = css({
8
+ display: "inline-flex",
9
+ alignItems: "center",
10
+ gap: "var(--tsu-spacing-sm)",
11
+ fontFamily: "inherit"
12
+ });
13
+ var sizeClasses = {
14
+ xs: css({
15
+ "--_pin-box": "var(--tsu-comp-height-xs)",
16
+ "--_pin-font": "var(--tsu-comp-font-size-xs)",
17
+ "--_pin-radius": "var(--tsu-radius-l1-xs)"
18
+ }),
19
+ sm: css({
20
+ "--_pin-box": "var(--tsu-comp-height-sm)",
21
+ "--_pin-font": "var(--tsu-comp-font-size-sm)",
22
+ "--_pin-radius": "var(--tsu-radius-l1-sm)"
23
+ }),
24
+ md: css({
25
+ "--_pin-box": "var(--tsu-comp-height-md)",
26
+ "--_pin-font": "var(--tsu-comp-font-size-md)",
27
+ "--_pin-radius": "var(--tsu-radius-l1-md)"
28
+ }),
29
+ lg: css({
30
+ "--_pin-box": "var(--tsu-comp-height-lg)",
31
+ "--_pin-font": "var(--tsu-comp-font-size-lg)",
32
+ "--_pin-radius": "var(--tsu-radius-l1-lg)"
33
+ }),
34
+ xl: css({
35
+ "--_pin-box": "var(--tsu-comp-height-xl)",
36
+ "--_pin-font": "var(--tsu-comp-font-size-xl)",
37
+ "--_pin-radius": "var(--tsu-radius-l1-xl)"
38
+ })
39
+ };
40
+ var variantClasses = {
41
+ solid: css({
42
+ backgroundColor: "var(--tsu-bg-control)",
43
+ border: "1px solid transparent"
44
+ }),
45
+ outline: css({ border: "1px solid var(--_pin-border)" })
46
+ };
47
+ var intentClasses = {
48
+ default: css({
49
+ "--_pin-border": "var(--tsu-accent-outline-border)",
50
+ "--_pin-border-focus": "var(--tsu-accent-outline-border-hover)"
51
+ }),
52
+ neutral: css({
53
+ "--_pin-border": "var(--tsu-neutral-outline-border)",
54
+ "--_pin-border-focus": "var(--tsu-neutral-outline-border-hover)"
55
+ })
56
+ };
57
+ var boxBaseClass = css({
58
+ width: "var(--_pin-box, var(--tsu-comp-height-md))",
59
+ height: "var(--_pin-box, var(--tsu-comp-height-md))",
60
+ padding: 0,
61
+ borderRadius: "var(--_pin-radius, var(--tsu-radius-l1-md))",
62
+ color: "var(--tsu-fg)",
63
+ fontFamily: "inherit",
64
+ fontSize: "var(--_pin-font, var(--tsu-comp-font-size-md))",
65
+ fontWeight: 600,
66
+ textAlign: "center",
67
+ WebkitAppearance: "none",
68
+ appearance: "none",
69
+ outline: "none",
70
+ touchAction: "manipulation",
71
+ transition: "border-color var(--tsu-transition-fast), background-color var(--tsu-transition-fast), box-shadow var(--tsu-transition-fast)",
72
+ "&:focus": {
73
+ borderColor: "var(--_pin-border-focus, var(--tsu-accent-outline-border-hover))",
74
+ backgroundColor: "var(--tsu-focus-bg)",
75
+ boxShadow: "0 0 0 1px var(--_pin-border-focus, var(--tsu-accent-outline-border-hover))"
76
+ }
77
+ });
78
+ var errorClass = css({
79
+ borderColor: "var(--tsu-danger-outline-border) !important",
80
+ "&:focus": {
81
+ borderColor: "var(--tsu-danger-outline-border) !important",
82
+ boxShadow: "0 0 0 1px var(--tsu-danger-outline-border) !important"
83
+ }
84
+ });
85
+ var disabledClass = css({
86
+ opacity: "var(--tsu-disabled-opacity)",
87
+ cursor: "not-allowed"
88
+ });
89
+ var PinInput = (props = {}) => {
90
+ const [local, rest] = splitProps(mergeProps$1({
91
+ length: 4,
92
+ size: "md",
93
+ type: "numeric",
94
+ variant: "outline",
95
+ intent: "default",
96
+ mask: false,
97
+ disabled: false,
98
+ invalid: false
99
+ }, props), [
100
+ "value",
101
+ "onValueChange",
102
+ "onComplete",
103
+ "length",
104
+ "size",
105
+ "type",
106
+ "variant",
107
+ "intent",
108
+ "mask",
109
+ "disabled",
110
+ "invalid",
111
+ "className",
112
+ "inputClassName"
113
+ ]);
114
+ const refs = {};
115
+ const boxSize = () => read(local.length) || 4;
116
+ const isNumeric = () => read(local.type) === "numeric";
117
+ const value = () => (read(local.value) || "").slice(0, boxSize());
118
+ const clean = (str) => {
119
+ if (!str) return "";
120
+ return isNumeric() ? str.replace(/\D/g, "") : str.replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
121
+ };
122
+ const focusBox = (index) => {
123
+ const el = refs[index];
124
+ if (el && typeof el.focus === "function") el.focus();
125
+ };
126
+ const commit = (next) => {
127
+ local.onValueChange?.(next);
128
+ if (next.length === boxSize()) local.onComplete?.(next);
129
+ };
130
+ const handleInput = (index) => (e) => {
131
+ const char = clean(e.target.value).slice(0, 1);
132
+ const base = value();
133
+ const next = base.slice(0, index) + char + base.slice(index + 1);
134
+ if (char && index < boxSize() - 1) focusBox(index + 1);
135
+ commit(next);
136
+ };
137
+ const handleKeyDown = (index) => (e) => {
138
+ if (e.key === "Backspace" && !e.target.value && index > 0) {
139
+ e.preventDefault();
140
+ const prev = index - 1;
141
+ const base = value();
142
+ commit(base.slice(0, prev) + base.slice(prev + 1));
143
+ focusBox(prev);
144
+ return;
145
+ }
146
+ if (e.key === "ArrowLeft" && index > 0) {
147
+ e.preventDefault();
148
+ focusBox(index - 1);
149
+ return;
150
+ }
151
+ if (e.key === "ArrowRight" && index < boxSize() - 1) {
152
+ e.preventDefault();
153
+ focusBox(index + 1);
154
+ return;
155
+ }
156
+ if (e.key === "Home") {
157
+ e.preventDefault();
158
+ focusBox(0);
159
+ return;
160
+ }
161
+ if (e.key === "End") {
162
+ e.preventDefault();
163
+ focusBox(boxSize() - 1);
164
+ }
165
+ };
166
+ const handlePaste = (index) => (e) => {
167
+ e.preventDefault();
168
+ const text = clean(e.clipboardData?.getData("text")?.trim());
169
+ if (!text) return;
170
+ const base = value();
171
+ commit((base.slice(0, index) + text + base.slice(index + text.length)).slice(0, boxSize()));
172
+ focusBox(Math.min(index + text.length, boxSize() - 1));
173
+ };
174
+ const handleFocus = (e) => e.target.select();
175
+ const boxClassName = (index) => {
176
+ const variant = read(local.variant) === "solid" ? "solid" : "outline";
177
+ let intent = read(local.intent);
178
+ if (intent === "neutral" && variant === "solid") intent = "default";
179
+ return [
180
+ boxBaseClass,
181
+ variantClasses[variant],
182
+ intentClasses[intent] ?? intentClasses.default,
183
+ read(local.invalid) && errorClass,
184
+ read(local.disabled) && disabledClass,
185
+ read(local.inputClassName)
186
+ ].filter(Boolean).join(" ");
187
+ };
188
+ return jsx("div", mergeProps({ className: () => [
189
+ rootClass,
190
+ sizeClasses[read(local.size)] ?? sizeClasses.md,
191
+ read(local.className)
192
+ ].filter(Boolean).join(" ") }, rest, { children: jsx(Loop, mergeProps({
193
+ each: () => Array.from({ length: boxSize() }),
194
+ children: (_, indexSignal) => {
195
+ const index = indexSignal();
196
+ return (() => {
197
+ const _el0 = _tmpl.cloneNode(true);
198
+ setProp(_el0, "key", () => index);
199
+ setProp(_el0, "ref", (el) => {
200
+ refs[index] = el;
201
+ });
202
+ setProp(_el0, "type", () => read(local.mask) ? "password" : "text");
203
+ setProp(_el0, "inputMode", () => isNumeric() ? "numeric" : void 0);
204
+ setProp(_el0, "autoComplete", () => index === 0 ? "one-time-code" : void 0);
205
+ setProp(_el0, "aria-label", () => `Character ${index + 1} of ${boxSize()}`);
206
+ setProp(_el0, "aria-invalid", () => read(local.invalid) ? "true" : "false");
207
+ setProp(_el0, "data-invalid", () => read(local.invalid) ? "" : void 0);
208
+ setProp(_el0, "value", () => {
209
+ read(local.mask);
210
+ return value()[index] || "";
211
+ });
212
+ setProp(_el0, "onInput", () => handleInput(index));
213
+ setProp(_el0, "onKeyDown", () => handleKeyDown(index));
214
+ setProp(_el0, "onPaste", () => handlePaste(index));
215
+ setProp(_el0, "onFocus", () => handleFocus);
216
+ setProp(_el0, "disabled", () => read(local.disabled));
217
+ setProp(_el0, "className", () => boxClassName(index));
218
+ return _el0;
219
+ })();
220
+ }
221
+ })) }));
222
+ };
223
+ //#endregion
224
+ export { PinInput as default };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ import Menu, { MenuContent, MenuItem, MenuSeparator, MenuTrigger } from "./compo
32
32
  import MoneyInput from "./components/MoneyInput.js";
33
33
  import NumberInput from "./components/NumberInput.js";
34
34
  import Pagination from "./components/Pagination.js";
35
+ import PinInput from "./components/PinInput.js";
35
36
  import Popover from "./components/Popover.js";
36
37
  import Presence_default from "./components/Presence.js";
37
38
  import Progress, { ProgressLabel } from "./components/Progress.js";
@@ -58,4 +59,4 @@ import ToggleGroup, { ToggleGroupItem } from "./components/ToggleGroup.js";
58
59
  import FilterGroup from "./components/FilterGroup.js";
59
60
  import Tour, { TourCloseTrigger, TourContent } from "./components/Tour.js";
60
61
  import TreeView, { TreeViewBranch, TreeViewBranchContent, TreeViewBranchControl, TreeViewBranchTrigger, TreeViewItem } from "./components/TreeView.js";
61
- export { AccordionRoot as Accordion, AccordionContent, AccordionItem, AccordionTrigger, Avatar, AvatarFallback, AvatarImage, BackToTop, Badge, BottomNavigation, BottomNavigationItem, BottomSheet, Button, Card, CardNumberInput, Carousel_default as Carousel, CarouselAutoplayTrigger, CarouselControl, CarouselIndicator, CarouselIndicatorGroup, CarouselItem, CarouselItemGroup, CarouselNextTrigger, CarouselPrevTrigger, CarouselProgressText, Checkbox, ClearTrigger, Clipboard, CloseButton, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorPicker, Root as Combobox, ComboboxContent, Input as ComboboxInput, ComboboxItem, ConfirmDialog, DatePicker, Dialog, Root$1 as Drawer, DrawerContent, DrawerTrigger, Field, Fieldset, FileUpload, FileUploadClearTrigger, FileUploadItem, FileUploadItemGroup, FilterGroup, FocusTrap_default as FocusTrap, Icon, Input$1 as Input, Link, Listbox, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, MoneyInput, NumberInput, Pagination, Popover, Portal_default as Portal, Presence_default as Presence, Progress, ProgressLabel, RadioGroup, RatingGroup, SafeArea, SearchInput, Select, SelectItem, SelectPc, SelectTrigger, SignaturePad, Skeleton, Slider, Spinner, Splitter, SplitterPanel, SplitterResizeTrigger, SplitterResizeTriggerIndicator, Steps, StickyHeader, SwipeReveal, Switch, Tabs, Tag, TagsInput, Toast, ToastCloseTrigger, ToastDescription, ToastIcon, ToastTitle, ToastToaster, Toggle, ToggleGroup, ToggleGroupItem, Tour, TourCloseTrigger, TourContent, TreeView, TreeViewBranch, TreeViewBranchContent, TreeViewBranchControl, TreeViewBranchTrigger, TreeViewItem, confirm, createToaster };
62
+ export { AccordionRoot as Accordion, AccordionContent, AccordionItem, AccordionTrigger, Avatar, AvatarFallback, AvatarImage, BackToTop, Badge, BottomNavigation, BottomNavigationItem, BottomSheet, Button, Card, CardNumberInput, Carousel_default as Carousel, CarouselAutoplayTrigger, CarouselControl, CarouselIndicator, CarouselIndicatorGroup, CarouselItem, CarouselItemGroup, CarouselNextTrigger, CarouselPrevTrigger, CarouselProgressText, Checkbox, ClearTrigger, Clipboard, CloseButton, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorPicker, Root as Combobox, ComboboxContent, Input as ComboboxInput, ComboboxItem, ConfirmDialog, DatePicker, Dialog, Root$1 as Drawer, DrawerContent, DrawerTrigger, Field, Fieldset, FileUpload, FileUploadClearTrigger, FileUploadItem, FileUploadItemGroup, FilterGroup, FocusTrap_default as FocusTrap, Icon, Input$1 as Input, Link, Listbox, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, MoneyInput, NumberInput, Pagination, PinInput, Popover, Portal_default as Portal, Presence_default as Presence, Progress, ProgressLabel, RadioGroup, RatingGroup, SafeArea, SearchInput, Select, SelectItem, SelectPc, SelectTrigger, SignaturePad, Skeleton, Slider, Spinner, Splitter, SplitterPanel, SplitterResizeTrigger, SplitterResizeTriggerIndicator, Steps, StickyHeader, SwipeReveal, Switch, Tabs, Tag, TagsInput, Toast, ToastCloseTrigger, ToastDescription, ToastIcon, ToastTitle, ToastToaster, Toggle, ToggleGroup, ToggleGroupItem, Tour, TourCloseTrigger, TourContent, TreeView, TreeViewBranch, TreeViewBranchContent, TreeViewBranchControl, TreeViewBranchTrigger, TreeViewItem, confirm, createToaster };
package/docs/api.md CHANGED
@@ -716,6 +716,8 @@ A free-form presentation layer: a backdrop, an optional drag grabber, and a scro
716
716
  **Rendered DOM parts:** exactly three — `data-part="backdrop"`, `data-part="grabber"` (only when `draggable`), and `data-part="content"`. With the default `lazyMount: true` + `unmountOnExit: true`, a closed sheet that was never opened renders **zero** nodes; after close it stays mounted only long enough for the exit transition, then unmounts. BottomSheet has no focus trap or Escape-to-close — use [`Dialog`](#dialog) when those are required.
717
717
 
718
718
  > **Design decision:** `content` is a pure container — `display: flex; flex-direction: column` only, with **no 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).
719
+ >
720
+ > **Consumer contract:** any scrollable child inside a `BottomSheet` must set `overscroll-behavior: contain` (or `none` if a hard edge is desired). Otherwise, on iOS the rubber-band overscroll at the child's top/bottom chains into the drag-to-dismiss area and shoves the whole sheet.
719
721
 
720
722
  | Prop | Type | Default | Description |
721
723
  |---|---|---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plastic-js/tsumiki",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "A UI component library for Plastic JS.",
5
5
  "license": "MIT",
6
6
  "author": "tigre",
@@ -1,45 +0,0 @@
1
- # Pagination renders an invisible ellipsis
2
-
3
- **Status:** Known issue
4
- **Affects:** `src/components/Pagination.jsx:55`
5
-
6
- ## Problem
7
-
8
- The `Pages` renderer emits the ellipsis without any content:
9
-
10
- ```jsx
11
- // src/components/Pagination.jsx:55
12
- : <ArkPagination.Ellipsis key={i} index={i} className={ellipsisClass} />
13
- ```
14
-
15
- `ArkPagination.Ellipsis` renders a plain empty `div` (it only spreads
16
- `getEllipsisProps` — id/data attrs — and never injects text). The tsumiki
17
- `ellipsisClass` only sizes/colors the box, so the ellipsis shows up as a blank
18
- gap with no `…` glyph.
19
-
20
- With enough pages to trigger the collapsed range, the page-number pattern looks
21
- random and buggy instead of standard, e.g. with 12 total pages and the default
22
- `boundaryCount = 1` / `siblingCount = 1`:
23
-
24
- - Page 1: `1 2 3 12` (should be `1 2 3 … 12`)
25
- - Page 4: `1 4 12` (should be `1 … 4 … 12`)
26
- - Page 10: `1 10 11 12` (should be `1 … 10 11 12`)
27
-
28
- Each gap is where the invisible ellipsis box sits.
29
-
30
- ## Fix
31
-
32
- Pass the glyph as children so the ellipsis is visible:
33
-
34
- ```jsx
35
- : <ArkPagination.Ellipsis key={i} index={i} className={ellipsisClass}>…</ArkPagination.Ellipsis>
36
- ```
37
-
38
- Consider tightening `ellipsisClass` width (e.g. `28px`) so the visible `…` does
39
- not consume a full button-sized slot.
40
-
41
- ## Secondary note (related UX)
42
-
43
- Prev/Next reuse `itemClass` (`minWidth: var(--tsu-level-primary-md)`, 40px),
44
- which is a small tap target. If wider triggers are desired, they need their own
45
- class, since `Pagination` currently exposes no per-trigger className override.