@plastic-js/tsumiki 0.1.81 → 0.1.83

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.
@@ -4,9 +4,7 @@ import { css } from "@emotion/css";
4
4
  import { splitProps } from "@plastic-js/plastic";
5
5
  //#region src/components/MoneyInput.jsx
6
6
  var _tmpl = template("<span></span>");
7
- var affixPrefixClass = css({ color: "var(--tsu-fg)" });
8
7
  var affixSuffixClass = css({
9
- color: "var(--tsu-fg)",
10
8
  fontWeight: "var(--tsu-font-weight-semibold)",
11
9
  paddingLeft: "10px",
12
10
  marginLeft: "var(--tsu-spacing-xs)",
@@ -42,7 +40,6 @@ var MoneyInput = (props) => {
42
40
  prefix: () => () => {
43
41
  const _el0 = _tmpl.cloneNode(true);
44
42
  insert(_el0, () => local.currency ?? "₱");
45
- setProp(_el0, "className", () => affixPrefixClass);
46
43
  return _el0;
47
44
  },
48
45
  suffix: () => {
@@ -0,0 +1,99 @@
1
+ import Input from "./Input.js";
2
+ import { insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
3
+ import { css } from "@emotion/css";
4
+ import { mergeProps as mergeProps$1, splitProps } from "@plastic-js/plastic";
5
+ //#region src/components/PhoneInput.jsx
6
+ var _tmpl = template("<span></span>");
7
+ var countryCodeClass = css({ fontVariantNumeric: "tabular-nums" });
8
+ var read = (value) => typeof value === "function" ? value() : value;
9
+ var PHONE_PRESETS = {
10
+ "+63": {
11
+ digits: 10,
12
+ format: "###-###-####",
13
+ placeholder: "9XX-XXX-XXXX"
14
+ },
15
+ "+86": {
16
+ digits: 11,
17
+ format: "###-####-####",
18
+ placeholder: "1XX-XXXX-XXXX"
19
+ }
20
+ };
21
+ var presetFor = (countryCode) => PHONE_PRESETS[countryCode] || PHONE_PRESETS["+63"];
22
+ var parseFormat = (format) => {
23
+ const groups = [];
24
+ const seps = [];
25
+ let n = 0;
26
+ for (const ch of format) {
27
+ if (ch === "#") {
28
+ n += 1;
29
+ continue;
30
+ }
31
+ groups.push(n);
32
+ seps.push(ch);
33
+ n = 0;
34
+ }
35
+ groups.push(n);
36
+ return {
37
+ groups,
38
+ seps
39
+ };
40
+ };
41
+ var applyFormat = (digits, format) => {
42
+ const { groups, seps } = parseFormat(format);
43
+ const total = groups.reduce((a, b) => a + b, 0);
44
+ const d = (digits || "").replace(/\D/g, "").slice(0, total);
45
+ if (!d) return "";
46
+ const parts = [];
47
+ let pos = 0;
48
+ for (const g of groups) {
49
+ parts.push(d.slice(pos, pos + g));
50
+ pos += g;
51
+ }
52
+ const out = [parts[0]];
53
+ for (let i = 1; i < parts.length; i++) {
54
+ if (!parts[i]) break;
55
+ out.push(seps[i - 1], parts[i]);
56
+ }
57
+ return out.join("");
58
+ };
59
+ var PhoneInput = (props) => {
60
+ const [local, rest] = splitProps(mergeProps$1({ countryCode: "+63" }, props), [
61
+ "value",
62
+ "onValueChange",
63
+ "invalid",
64
+ "disabled",
65
+ "placeholder",
66
+ "countryCode"
67
+ ]);
68
+ const preset = () => presetFor(read(local.countryCode));
69
+ const placeholder = () => read(local.placeholder) ?? preset().placeholder;
70
+ const handleInput = (e) => {
71
+ const { digits } = presetFor(read(local.countryCode));
72
+ local.onValueChange?.(e.target.value.replace(/\D/g, "").slice(0, digits));
73
+ };
74
+ return jsx(Input, mergeProps({
75
+ type: "tel",
76
+ inputMode: "numeric",
77
+ maxLength: () => preset().format.length,
78
+ get invalid() {
79
+ return local.invalid;
80
+ },
81
+ get disabled() {
82
+ return local.disabled;
83
+ },
84
+ placeholder,
85
+ prefix: () => {
86
+ const cc = read(local.countryCode);
87
+ return cc ? () => {
88
+ const _el0 = _tmpl.cloneNode(true);
89
+ insert(_el0, () => cc);
90
+ setProp(_el0, "className", () => countryCodeClass);
91
+ return _el0;
92
+ } : null;
93
+ },
94
+ value: () => applyFormat(read(local.value), preset().format),
95
+ onInput: handleInput
96
+ }, rest));
97
+ };
98
+ //#endregion
99
+ export { PhoneInput as default };
@@ -116,7 +116,7 @@ var listClass = css({
116
116
  minHeight: 0,
117
117
  overflowY: "auto",
118
118
  WebkitOverflowScrolling: "touch",
119
- padding: "0 8px calc(8px + env(safe-area-inset-bottom, 0px))"
119
+ padding: "0 8px max(var(--tsu-container-padding-lg), env(safe-area-inset-bottom, 0px))"
120
120
  });
121
121
  var itemClass = css({
122
122
  width: "100%",
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ import MoneyInput from "./components/MoneyInput.js";
33
33
  import NumberInput from "./components/NumberInput.js";
34
34
  import Pagination from "./components/Pagination.js";
35
35
  import PinInput from "./components/PinInput.js";
36
+ import PhoneInput from "./components/PhoneInput.js";
36
37
  import Popover from "./components/Popover.js";
37
38
  import Presence_default from "./components/Presence.js";
38
39
  import Progress, { ProgressLabel } from "./components/Progress.js";
@@ -59,4 +60,4 @@ import ToggleGroup, { ToggleGroupItem } from "./components/ToggleGroup.js";
59
60
  import FilterGroup from "./components/FilterGroup.js";
60
61
  import Tour, { TourCloseTrigger, TourContent } from "./components/Tour.js";
61
62
  import TreeView, { TreeViewBranch, TreeViewBranchContent, TreeViewBranchControl, TreeViewBranchTrigger, TreeViewItem } from "./components/TreeView.js";
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 };
63
+ 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, PhoneInput, 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
@@ -46,6 +46,24 @@ Every Ark-backed component exposes `.origin` — a mapping to the raw `@plastic-
46
46
 
47
47
  The shared `size` prop accepts: `xs | sm | md | lg | xl | xxl` (default `md`).
48
48
 
49
+ ### Bottom safe-area inset — `max()`, never `sum`
50
+
51
+ Any surface that sits flush to the bottom of the screen (a bottom sheet panel, a scrollable option list inside one) clears the iOS home indicator with `max(<constant>, env(safe-area-inset-bottom, 0px))` — it picks whichever is larger, it never **adds** the safe-area inset to the component's own padding:
52
+
53
+ ```css
54
+ /* Dialog sheet panel — src/components/DialogSheet.jsx */
55
+ padding-bottom: max(var(--tsu-container-padding-lg), env(safe-area-inset-bottom, 0px));
56
+
57
+ /* Select option list — src/components/Select.jsx */
58
+ padding: 0 8px max(var(--tsu-container-padding-lg), env(safe-area-inset-bottom, 0px));
59
+ ```
60
+
61
+ `<constant>` is the same token on every flush-bottom surface — `--tsu-container-padding-lg` (24px) — so the Select list and the Dialog sheet share one bottom rhythm. It keeps the regular gap on devices without an inset (`env()` resolves to 0, so the constant wins) and expands to the safe-area height on iOS.
62
+
63
+ **Never write `calc(<constant> + env(safe-area-inset-bottom))`.** CSS padding does not collapse — summing the two stacks them into an oversized gap under the last row on inset devices (e.g. `8px + 34px` instead of `34px`). This is the same class of bug the Dialog footer's zeroed padding avoids (see `sheetFooterClass`).
64
+
65
+ > Applies to flush-bottom surfaces only (`Dialog` in `sheet` mode, the `Select` option list). Centered overlays and non-flush components have no home-indicator clearance. `DatePicker`'s wheel sheet keeps its own inset handling and is intentionally excluded from this rule.
66
+
49
67
  ---
50
68
 
51
69
  ## Semantic tokens
@@ -169,6 +187,30 @@ Currency-formatted numeric input.
169
187
  | `placeholder` | `string` | — | Placeholder text. |
170
188
  | `disabled` | `boolean` | `false` | Disabled. |
171
189
 
190
+ ### PhoneInput
191
+
192
+ International phone input backed by a country preset map. Each preset defines how many digits the number has, the display `format` (mask of `#` + separators) and the empty-state `placeholder`. Typed digits are capped to the preset length, stored raw (no country prefix, no separators), and rendered with the preset format. The input renders `type="tel"`, `inputMode="numeric"` and a `maxLength` matching the fully formatted value.
193
+
194
+ **Exports:** `PhoneInput`
195
+
196
+ | Prop | Type | Default | Description |
197
+ |---|---|---|---|
198
+ | `value` | `string \| (() => string)` | — | Phone number (digits only, no country prefix/separators). |
199
+ | `onValueChange` | `(value) => void` | — | Called with the raw digits (capped to the preset length). |
200
+ | `countryCode` | `string \| (() => string)` | `'+63'` | Country code — selects the preset and renders as the prefix adornment. |
201
+ | `invalid` | `boolean` | `false` | Error state. |
202
+ | `disabled` | `boolean` | `false` | Disabled. |
203
+ | `placeholder` | `string` | preset | Placeholder text shown while empty (overrides the preset). |
204
+
205
+ **Supported presets** (add more entries to the internal map to extend):
206
+
207
+ | Country code | Digits | Format | Placeholder |
208
+ |---|---|---|---|
209
+ | `+63` (Philippines) | 10 | `###-###-####` | `9XX-XXX-XXXX` |
210
+ | `+86` (China) | 11 | `###-####-####` | `1XX-XXXX-XXXX` |
211
+
212
+ > The controlled value holds the raw digits only — read it directly (or strip non-digits) for the payload. Passing an unknown `countryCode` keeps the given prefix but falls back to the `+63` formatting preset.
213
+
172
214
  ### NumberInput
173
215
 
174
216
  **Exports:** `NumberInput` · Escape hatch: `NumberInput.origin.{Root, DecrementTrigger, Input, IncrementTrigger}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plastic-js/tsumiki",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "description": "A UI component library for Plastic JS.",
5
5
  "license": "MIT",
6
6
  "author": "tigre",