@plastic-js/tsumiki 0.1.13 → 0.1.15
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.
- package/README.md +130 -1
- package/dist/components/{FilteredSelect.js → FilterableSelect.js} +4 -4
- package/dist/components/FilterableSelect.js.map +1 -0
- package/dist/components/FilterableSelectMobile.js +122 -0
- package/dist/components/FilterableSelectMobile.js.map +1 -0
- package/dist/components/SelectMobile/anatomy.js +10 -0
- package/dist/components/SelectMobile/anatomy.js.map +1 -0
- package/dist/components/SelectMobile/index.js +310 -0
- package/dist/components/SelectMobile/index.js.map +1 -0
- package/dist/components/SwipeReveal.js +34 -13
- package/dist/components/SwipeReveal.js.map +1 -1
- package/dist/index.js +4 -2
- package/package.json +1 -1
- package/dist/components/FilteredSelect.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,9 +11,138 @@ npm install @plastic-js/tsumiki
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```js
|
|
14
|
-
import { Dialog, Combobox, Tooltip } from '@plastic-js/tsumiki'
|
|
14
|
+
import { Dialog, Combobox, Tooltip, SelectMobile, FilterableSelectMobile } from '@plastic-js/tsumiki'
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Components
|
|
18
|
+
|
|
19
|
+
### SelectMobile
|
|
20
|
+
|
|
21
|
+
A mobile-optimized bottom-sheet select built with a compound parts pattern. Renders a trigger button and a draggable sheet overlay — ideal for touch interfaces.
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
import { createSignal } from '@plastic-js/plastic'
|
|
25
|
+
import { SelectMobile } from '@plastic-js/tsumiki'
|
|
26
|
+
|
|
27
|
+
function Example(){
|
|
28
|
+
const value = createSignal(null)
|
|
29
|
+
const items = [
|
|
30
|
+
{ value: 'tpe', label: 'Taipei' },
|
|
31
|
+
{ value: 'kxg', label: 'Kaohsiung' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<SelectMobile.Root
|
|
36
|
+
value={value}
|
|
37
|
+
onValueChange={v => value(v)}
|
|
38
|
+
items={items}
|
|
39
|
+
>
|
|
40
|
+
<SelectMobile.Trigger placeholder='Choose a city' />
|
|
41
|
+
<SelectMobile.Content />
|
|
42
|
+
</SelectMobile.Root>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**SelectMobile.Root props:**
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `value` | `string \| (() => string)` | `''` | Current selected value |
|
|
52
|
+
| `onValueChange` | `(value: string) => void` | — | Called when an item is selected |
|
|
53
|
+
| `items` | `array \| (() => array)` | `[]` | Data array |
|
|
54
|
+
| `itemToValue` | `(item) => string` | `item.value` | Extract value from an item |
|
|
55
|
+
| `itemToLabel` | `(item) => node` | `item.label` | Extract label from an item |
|
|
56
|
+
| `open` | `(() => boolean)` | — | Controlled open state (getter) |
|
|
57
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
|
|
58
|
+
| `onOpenChange` | `(isOpen: boolean) => void` | — | Called when open state changes |
|
|
59
|
+
|
|
60
|
+
**SelectMobile.Trigger props:**
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Description |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `placeholder` | `string` | `'Select'` | Placeholder text when no item is selected |
|
|
65
|
+
| `className` | `string` | — | CSS class for the trigger button |
|
|
66
|
+
| `children` | `node` | — | Custom trigger content (replaces default label + chevron) |
|
|
67
|
+
|
|
68
|
+
> **Note on Trigger element:** The trigger renders `<div role="button" tabIndex={0}>` instead of a native `<button>`.
|
|
69
|
+
> This is a workaround for a **Chrome iOS (WebKit) bug**: tapping a native `<button>` inside a scrollable container
|
|
70
|
+
> with `-webkit-overflow-scrolling: touch` permanently pins `activeElement` to the button. After the trigger opens
|
|
71
|
+
> the sheet, any subsequent `focus()` call on the filter `<input>` inside the list is silently ignored.
|
|
72
|
+
> A `<div role="button">` is semantically equivalent for accessibility but avoids this WebKit focus-lock behaviour.
|
|
73
|
+
>
|
|
74
|
+
> Typically a native `<button>` would be preferred for keyboard tab navigation (`tabIndex` works natively).
|
|
75
|
+
> On mobile, however, keyboard tab navigation is irrelevant — the sheet is operated via touch, and the Escape key
|
|
76
|
+
> for closing is handled by the `Dialog`-like overlay, not by tab order. The `tabIndex={0}` on the `<div>` preserves
|
|
77
|
+
> keyboard discoverability for assistive technology while working around the iOS focus trap.
|
|
78
|
+
|
|
79
|
+
**SelectMobile.Content props:**
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Default | Description |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `clearable` | `boolean` | `false` | Show a "clear selection" row at the top |
|
|
84
|
+
| `clearLabel` | `string` | `'None'` | Label for the clear row |
|
|
85
|
+
| `className` | `string` | — | CSS class for the sheet panel |
|
|
86
|
+
| `backdropClassName` | `string` | — | CSS class for the backdrop overlay |
|
|
87
|
+
| `backdropStyle` | `object` | — | Inline style for the backdrop overlay |
|
|
88
|
+
|
|
89
|
+
**SelectMobile.Item props:**
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `item` | `any` | — | Item from the `items` array (resolves value/label automatically) |
|
|
94
|
+
| `value` | `string` | — | Explicit value (use when children are custom) |
|
|
95
|
+
| `className` | `string` | — | CSS class for the item button |
|
|
96
|
+
| `children` | `node` | — | Custom item content |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### FilterableSelectMobile
|
|
101
|
+
|
|
102
|
+
A mobile bottom-sheet select with a filter input at the top of the list. Wraps `SelectMobile` internally and exposes a flat single-component API.
|
|
103
|
+
|
|
104
|
+
```jsx
|
|
105
|
+
import { createSignal } from '@plastic-js/plastic'
|
|
106
|
+
import { FilterableSelectMobile } from '@plastic-js/tsumiki'
|
|
107
|
+
|
|
108
|
+
function Example(){
|
|
109
|
+
const value = createSignal(null)
|
|
110
|
+
const cities = [
|
|
111
|
+
{ value: 'tpe', label: 'Taipei' },
|
|
112
|
+
{ value: 'kxg', label: 'Kaohsiung' },
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<FilterableSelectMobile
|
|
117
|
+
items={cities}
|
|
118
|
+
itemToValue={item => item.value}
|
|
119
|
+
itemToLabel={item => item.label}
|
|
120
|
+
value={value}
|
|
121
|
+
onValueChange={v => value(v)}
|
|
122
|
+
placeholder='Search city…'
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Props:**
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
|---|---|---|---|
|
|
132
|
+
| `items` | `array \| (() => array)` | `[]` | Data array |
|
|
133
|
+
| `value` | `string \| (() => string)` | `''` | Current selected value |
|
|
134
|
+
| `onValueChange` | `(value: string) => void` | — | Called when an item is selected |
|
|
135
|
+
| `itemToValue` | `(item) => string` | `item.value ?? item` | Extract value from an item |
|
|
136
|
+
| `itemToLabel` | `(item) => string` | `item.label ?? String(item)` | Extract label from an item |
|
|
137
|
+
| `placeholder` | `string` | `'Select'` | Placeholder text on the trigger |
|
|
138
|
+
| `filter` | `(item, query, itemToLabel) => boolean` | substring match | Custom filter function |
|
|
139
|
+
| `clearable` | `boolean` | `false` | Show a "clear" row at the top |
|
|
140
|
+
| `clearLabel` | `string` | `'None'` | Label for the clear row |
|
|
141
|
+
| `backdropClassName` | `string` | — | CSS class for the backdrop overlay |
|
|
142
|
+
| `backdropStyle` | `object` | — | Inline style for the backdrop overlay |
|
|
143
|
+
|
|
144
|
+
The filter input is auto-focused when the sheet opens and cleared when it closes.
|
|
145
|
+
|
|
17
146
|
## License
|
|
18
147
|
|
|
19
148
|
MIT
|
|
@@ -2,7 +2,7 @@ import { Combobox, useComboboxContext } from "@plastic-js/ark";
|
|
|
2
2
|
import { insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
3
3
|
import { Loop, createComputed, createContext, createEffect, createSignal, splitProps, useContext } from "@plastic-js/plastic";
|
|
4
4
|
import { collection } from "@zag-js/combobox";
|
|
5
|
-
//#region src/components/
|
|
5
|
+
//#region src/components/FilterableSelect.jsx
|
|
6
6
|
var _tmpl3 = template("<span></span>");
|
|
7
7
|
var _tmpl2 = template("<svg fill=\"none\" stroke=\"currentColor\" strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>");
|
|
8
8
|
var _tmpl = template("<svg fill=\"none\" stroke=\"currentColor\" strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" viewBox=\"0 0 24 24\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>");
|
|
@@ -88,7 +88,7 @@ function ChevronIcon() {
|
|
|
88
88
|
function CheckIcon() {
|
|
89
89
|
return _tmpl2.cloneNode(true);
|
|
90
90
|
}
|
|
91
|
-
var
|
|
91
|
+
var FilterableSelect = (props = {}) => {
|
|
92
92
|
const [local, rest] = splitProps(props, [
|
|
93
93
|
"items",
|
|
94
94
|
"itemToString",
|
|
@@ -176,6 +176,6 @@ var FilteredSelect = (props = {}) => {
|
|
|
176
176
|
] }));
|
|
177
177
|
};
|
|
178
178
|
//#endregion
|
|
179
|
-
export {
|
|
179
|
+
export { FilterableSelect as default };
|
|
180
180
|
|
|
181
|
-
//# sourceMappingURL=
|
|
181
|
+
//# sourceMappingURL=FilterableSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterableSelect.js","names":["Combobox","useComboboxContext","Loop","createComputed","createContext","createEffect","createSignal","splitProps","useContext","collection","createCollection","_tmpl3","_template","_tmpl2","_tmpl","access","value","FilterContext","defaultItemToValue","item","String","id","defaultItemToString","label","name","defaultFilter","query","itemToString","toLowerCase","includes","Root","props","local","rest","itemToValue","filter","rawValue","normalizedValue","handleValueChange","v","length","onValueChange","inputValue","filteredItems","q","trim","list","items","_jsx","Provider","_mergeProps","children","onInputValueChange","HighlightSingleMatch","combobox","api","open","highlightedValue","setHighlightValue","Input","onInput","event","getInputProps","onChange","Items","each","ChevronIcon","cloneNode","CheckIcon","FilterableSelect","cn","classNames","openOnClick","Label","className","Control","control","input","disabled","placeholder","Trigger","trigger","type","Content","content","Empty","empty","emptyText","Item","key","ItemText","itemText","_el0","_insert","_setProp","check"],"sources":["../../src/components/FilterableSelect.jsx"],"sourcesContent":["import { Combobox, useComboboxContext } from '@plastic-js/ark'\nimport {\n\tLoop, createComputed, createContext, createEffect, createSignal, splitProps, useContext,\n} from '@plastic-js/plastic'\nimport { collection as createCollection } from '@zag-js/combobox'\n\n// FilterableSelect — a filterable single-select built on the pure `Combobox`\n// primitive's underlying headless machine.\n//\n// This file owns two things:\n// 1. The filter plumbing. The headless component is \"controlled collection\"\n// only: it expects you to build a @zag-js collection yourself and narrow it\n// down as the user types. The internal <Root> keeps a query in sync with the\n// input, filters the list and feeds a reactive collection back to the\n// headless Root.\n// 2. The public API / contract. The default-exported <FilterableSelect> assembles\n// the full structure (label, control, input, trigger, content, items) and\n// exposes a flat prop API. It is intentionally style-agnostic: every visual\n// slot is themed through the `classNames` map, so the styling layer\n// only has to supply CSS.\n\nconst access = (value)=> {\n\treturn typeof value === 'function' ? value() : value\n}\n\n// Exposes the filtered items to <Items> without forcing the caller to thread\n// them through props.\nconst FilterContext = createContext(null)\n\nconst defaultItemToValue = item=> String(item?.value ?? item?.id ?? item)\nconst defaultItemToString = item=> String(item?.label ?? item?.name ?? item)\n\n// Substring match on the item's display string. Override via the `filter` prop\n// for fuzzy/score-based matching.\nconst defaultFilter = (item, query, itemToString)=> itemToString(item).toLowerCase().includes(query)\n\nconst Root = (props = {})=> {\n\tconst [local, rest] = splitProps(props, [\n\t\t'items', 'itemToValue', 'itemToString', 'filter', 'inputValue', 'onInputValueChange', 'children',\n\t])\n\n\tconst itemToValue = local.itemToValue ?? defaultItemToValue\n\tconst itemToString = local.itemToString ?? defaultItemToString\n\tconst filter = local.filter ?? defaultFilter\n\n\t// zag-js/combobox expects value to be an array (it calls .join(\",\")).\n\t// Single-select means a 0‑ or 1‑element array.\n\tconst rawValue = access(rest.value)\n\tconst normalizedValue = rawValue == null ? [] : [rawValue]\n\n\tconst handleValueChange = (value)=> {\n\t\tconst v = value.length >= 1 ? value[0] : null\n\t\trest.onValueChange?.(v)\n\t}\n\n\t// Internal query mirrors the input. We seed it from any `inputValue` the\n\t// caller passed so an initial/controlled value still filters correctly.\n\tconst query = createSignal(access(local.inputValue) ?? '')\n\n\t// Recomputes on typing and whenever the source `items` change. An empty\n\t// query shows everything; otherwise only the matches survive.\n\tconst filteredItems = createComputed(()=> {\n\t\tconst q = query().trim().toLowerCase()\n\t\tconst list = access(local.items) ?? []\n\t\treturn q ? list.filter(item=> filter(item, q, itemToString)) : list\n\t})\n\n\tconst collection = createComputed(()=> createCollection({\n\t\titems: filteredItems(),\n\t\titemToValue,\n\t\titemToString,\n\t}))\n\n\treturn (\n\t\t<FilterContext.Provider value={filteredItems}>\n\t\t\t<Combobox.Root\n\t\t\t\t{...rest}\n\t\t\t\tvalue={normalizedValue}\n\t\t\t\tonValueChange={handleValueChange}\n\t\t\t\tcollection={collection}\n\t\t\t\tinputValue={query}\n\t\t\t\tonInputValueChange={(value)=> {\n\t\t\t\t\tquery(value)\n\t\t\t\t\tlocal.onInputValueChange?.(value)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<HighlightSingleMatch itemToValue={itemToValue} items={filteredItems} />\n\t\t\t\t{local.children}\n\t\t\t</Combobox.Root>\n\t\t</FilterContext.Provider>\n\t)\n}\n\n// When the open list narrows to a single match, pre-highlight it so the user\n// can confirm with Enter/Tab without arrowing down. We nudge the machine's own\n// (uncontrolled) highlight imperatively rather than controlling `highlightedValue`\n// — controlling it would pin the highlight in place and fight the machine, e.g.\n// blocking the highlight-clear on the next keystroke and the select-and-close on\n// Enter. We only set it; the machine still clears/moves it on input and navigation.\nconst HighlightSingleMatch = (props = {})=> {\n\tconst combobox = useComboboxContext()\n\tcreateEffect(()=> {\n\t\tconst api = combobox()\n\t\tconst list = props.items()\n\t\tif (!api.open || list.length !== 1){ return }\n\t\tconst value = props.itemToValue(list[0])\n\t\tif (api.highlightedValue !== value){ api.setHighlightValue(value) }\n\t})\n\treturn null\n}\n\n// The headless input only fires zag's INPUT.CHANGE on the native `change`\n// event — which, unlike React's onChange, fires on blur, not per keystroke. So\n// typing never reached the machine and the list never filtered. We bridge the\n// per-keystroke `input` event to zag's own onChange handler so every character\n// drives the same INPUT.CHANGE (open + filter) behaviour.\nconst Input = (props = {})=> {\n\tconst combobox = useComboboxContext()\n\treturn (\n\t\t<Combobox.Input\n\t\t\t{...props}\n\t\t\tonInput={event=> combobox().getInputProps().onChange?.(event)}\n\t\t/>\n\t)\n}\n\n// Render-prop over the filtered items. Place inside <Combobox.Content> and\n// return a <Combobox.Item> for each match. Backed by <Loop> so the list stays\n// reactive — a bare thunk returned from a component is unwrapped once and would\n// never re-filter.\nconst Items = (props = {})=> {\n\tconst items = useContext(FilterContext)\n\treturn (\n\t\t<Loop each={items ?? []}>\n\t\t\t{item=> props.children?.(item)}\n\t\t</Loop>\n\t)\n}\n\nfunction ChevronIcon(){\n\treturn (\n\t\t<svg\n\t\t\tfill='none' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round'\n\t\t\tstrokeWidth='2' viewBox='0 0 24 24'\n\t\t>\n\t\t\t<polyline points='6 9 12 15 18 9' />\n\t\t</svg>\n\t)\n}\n\nfunction CheckIcon(){\n\treturn (\n\t\t<svg\n\t\t\tfill='none' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round'\n\t\t\tstrokeWidth='2.5' viewBox='0 0 24 24'\n\t\t>\n\t\t\t<polyline points='20 6 9 17 4 12' />\n\t\t</svg>\n\t)\n}\n\n// Public component. `classNames` themes each slot (control/input/trigger/content/\n// empty/item/itemText/check/label); anything not split here (e.g. `filter`,\n// `name`) flows through to the filter Root.\nconst FilterableSelect = (props = {})=> {\n\tconst [local, rest] = splitProps(props, [\n\t\t'items',\n\t\t'itemToString',\n\t\t'itemToValue',\n\t\t'placeholder',\n\t\t'label',\n\t\t'openOnClick',\n\t\t'disabled',\n\t\t'classNames',\n\t\t'emptyText',\n\t])\n\n\tconst cn = local.classNames ?? {}\n\n\t// `value`, `onValueChange`, `filter`, `name`, … are not transformed here, so\n\t// they ride `rest` straight through to the underlying machine.\n\treturn (\n\t\t<Root\n\t\t\titemToString={local.itemToString}\n\t\t\titemToValue={local.itemToValue}\n\t\t\titems={local.items}\n\t\t\topenOnClick={local.openOnClick ?? true}\n\t\t\t{...rest}\n\t\t>\n\t\t\t{local.label && (\n\t\t\t\t<Combobox.Label className={cn.label}>\n\t\t\t\t\t{local.label}\n\t\t\t\t</Combobox.Label>\n\t\t\t)}\n\t\t\t<Combobox.Control className={cn.control}>\n\t\t\t\t<Input\n\t\t\t\t\tclassName={cn.input}\n\t\t\t\t\tdisabled={local.disabled}\n\t\t\t\t\tplaceholder={local.placeholder ?? 'Type to search...'}\n\t\t\t\t/>\n\t\t\t\t<Combobox.Trigger className={cn.trigger} type='button'>\n\t\t\t\t\t<ChevronIcon />\n\t\t\t\t</Combobox.Trigger>\n\t\t\t</Combobox.Control>\n\t\t\t<Combobox.Content className={cn.content}>\n\t\t\t\t<Combobox.Empty className={cn.empty}>\n\t\t\t\t\t{local.emptyText ?? 'No results found'}\n\t\t\t\t</Combobox.Empty>\n\t\t\t\t<Items>\n\t\t\t\t\t{item=> (\n\t\t\t\t\t\t<Combobox.Item className={cn.item} item={item} key={local.itemToValue?.(item) ?? item}>\n\t\t\t\t\t\t\t<Combobox.ItemText className={cn.itemText}>\n\t\t\t\t\t\t\t\t{local.itemToString?.(item) ?? String(item)}\n\t\t\t\t\t\t\t</Combobox.ItemText>\n\t\t\t\t\t\t\t<span className={cn.check}>\n\t\t\t\t\t\t\t\t<CheckIcon />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</Combobox.Item>\n\t\t\t\t\t)}\n\t\t\t\t</Items>\n\t\t\t</Combobox.Content>\n\t\t</Root>\n\t)\n}\n\nexport default FilterableSelect\n"],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import SelectMobile from "./SelectMobile/index.js";
|
|
2
|
+
import { jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
3
|
+
import { css } from "@emotion/css";
|
|
4
|
+
import { createSignal, splitProps } from "@plastic-js/plastic";
|
|
5
|
+
//#region src/components/FilterableSelectMobile.jsx
|
|
6
|
+
var _tmpl2 = template("<div>No results</div>");
|
|
7
|
+
var _tmpl = template("<input type=\"text\" placeholder=\"Filter…\">");
|
|
8
|
+
var inputClass = css({
|
|
9
|
+
width: "100%",
|
|
10
|
+
height: "42px",
|
|
11
|
+
padding: "0 14px",
|
|
12
|
+
marginBottom: "6px",
|
|
13
|
+
background: "rgba(255,255,255,0.06)",
|
|
14
|
+
border: "1px solid var(--border)",
|
|
15
|
+
borderRadius: "10px",
|
|
16
|
+
color: "var(--ink)",
|
|
17
|
+
fontSize: "16px",
|
|
18
|
+
fontFamily: "inherit",
|
|
19
|
+
outline: "none",
|
|
20
|
+
boxSizing: "border-box",
|
|
21
|
+
"&:focus": { borderColor: "var(--accent)" },
|
|
22
|
+
"&::placeholder": { color: "var(--muted)" }
|
|
23
|
+
});
|
|
24
|
+
var noResultsClass = css({
|
|
25
|
+
padding: "20px 14px",
|
|
26
|
+
textAlign: "center",
|
|
27
|
+
color: "var(--muted)",
|
|
28
|
+
fontSize: "14px"
|
|
29
|
+
});
|
|
30
|
+
var defaultItemToValue = (item) => item?.value ?? item;
|
|
31
|
+
var defaultItemToLabel = (item) => item?.label ?? String(item);
|
|
32
|
+
var defaultFilter = (item, query, itemToLabel) => itemToLabel(item).toLowerCase().includes(query);
|
|
33
|
+
var FilterableSelectMobile = (props) => {
|
|
34
|
+
const [local] = splitProps(props, [
|
|
35
|
+
"items",
|
|
36
|
+
"value",
|
|
37
|
+
"onValueChange",
|
|
38
|
+
"itemToValue",
|
|
39
|
+
"itemToLabel",
|
|
40
|
+
"placeholder",
|
|
41
|
+
"filter",
|
|
42
|
+
"clearable",
|
|
43
|
+
"clearLabel",
|
|
44
|
+
"backdropClassName",
|
|
45
|
+
"backdropStyle"
|
|
46
|
+
]);
|
|
47
|
+
const itemToValue = local.itemToValue ?? defaultItemToValue;
|
|
48
|
+
const itemToLabel = local.itemToLabel ?? defaultItemToLabel;
|
|
49
|
+
const filter = local.filter ?? defaultFilter;
|
|
50
|
+
const query = createSignal("");
|
|
51
|
+
let inputEl = null;
|
|
52
|
+
const filteredItems = () => {
|
|
53
|
+
const q = query().trim().toLowerCase();
|
|
54
|
+
const list = typeof local.items === "function" ? local.items() : local.items ?? [];
|
|
55
|
+
return q ? list.filter((item) => filter(item, q, itemToLabel)) : list;
|
|
56
|
+
};
|
|
57
|
+
const handleOpenChange = (isOpen) => {
|
|
58
|
+
if (isOpen) queueMicrotask(() => inputEl?.focus());
|
|
59
|
+
else query("");
|
|
60
|
+
};
|
|
61
|
+
return jsx(SelectMobile.Root, mergeProps({
|
|
62
|
+
get value() {
|
|
63
|
+
return local.value;
|
|
64
|
+
},
|
|
65
|
+
get onValueChange() {
|
|
66
|
+
return local.onValueChange;
|
|
67
|
+
},
|
|
68
|
+
get items() {
|
|
69
|
+
return local.items;
|
|
70
|
+
},
|
|
71
|
+
itemToValue,
|
|
72
|
+
itemToLabel,
|
|
73
|
+
onOpenChange: handleOpenChange,
|
|
74
|
+
children: [jsx(SelectMobile.Trigger, mergeProps({ get placeholder() {
|
|
75
|
+
return local.placeholder;
|
|
76
|
+
} })), jsx(SelectMobile.Content, mergeProps({
|
|
77
|
+
get clearable() {
|
|
78
|
+
return local.clearable;
|
|
79
|
+
},
|
|
80
|
+
get clearLabel() {
|
|
81
|
+
return local.clearLabel;
|
|
82
|
+
},
|
|
83
|
+
get backdropClassName() {
|
|
84
|
+
return local.backdropClassName;
|
|
85
|
+
},
|
|
86
|
+
get backdropStyle() {
|
|
87
|
+
return local.backdropStyle;
|
|
88
|
+
},
|
|
89
|
+
children: [(() => {
|
|
90
|
+
const _el0 = _tmpl.cloneNode(true);
|
|
91
|
+
setProp(_el0, "ref", (el) => {
|
|
92
|
+
inputEl = el;
|
|
93
|
+
});
|
|
94
|
+
setProp(_el0, "className", () => inputClass);
|
|
95
|
+
setProp(_el0, "value", () => query);
|
|
96
|
+
setProp(_el0, "onInput", (e) => query(e.target.value));
|
|
97
|
+
setProp(_el0, "onClick", (e) => e.stopPropagation());
|
|
98
|
+
setProp(_el0, "onPointerDown", (e) => {
|
|
99
|
+
e.target.focus({ preventScroll: true });
|
|
100
|
+
});
|
|
101
|
+
return _el0;
|
|
102
|
+
})(), () => {
|
|
103
|
+
const list = filteredItems();
|
|
104
|
+
if (list.length === 0) return (() => {
|
|
105
|
+
const _el0 = _tmpl2.cloneNode(true);
|
|
106
|
+
setProp(_el0, "className", () => noResultsClass);
|
|
107
|
+
return _el0;
|
|
108
|
+
})();
|
|
109
|
+
return list.map((item) => jsx(SelectMobile.Item, mergeProps({
|
|
110
|
+
item,
|
|
111
|
+
get key() {
|
|
112
|
+
return itemToValue(item);
|
|
113
|
+
}
|
|
114
|
+
})));
|
|
115
|
+
}]
|
|
116
|
+
}))]
|
|
117
|
+
}));
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
export { FilterableSelectMobile as default };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=FilterableSelectMobile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterableSelectMobile.js","names":["css","createSignal","splitProps","SelectMobile","_tmpl2","_template","_tmpl","inputClass","width","height","padding","marginBottom","background","border","borderRadius","color","fontSize","fontFamily","outline","boxSizing","borderColor","noResultsClass","textAlign","defaultItemToValue","item","value","defaultItemToLabel","label","String","defaultFilter","query","itemToLabel","toLowerCase","includes","FilterableSelectMobile","props","local","itemToValue","filter","inputEl","filteredItems","q","trim","list","items","handleOpenChange","isOpen","queueMicrotask","focus","_jsx","Root","_mergeProps","onValueChange","onOpenChange","children","Trigger","placeholder","Content","clearable","clearLabel","backdropClassName","backdropStyle","_el0","cloneNode","_setProp","el","e","target","stopPropagation","preventScroll","length","map","Item","key"],"sources":["../../src/components/FilterableSelectMobile.jsx"],"sourcesContent":["import { css } from '@emotion/css'\nimport { createSignal, splitProps } from '@plastic-js/plastic'\nimport { SelectMobile } from './SelectMobile/index.jsx'\n\n// ── Styles ───────────────────────────────────────────────────────────────\nconst inputClass = css({\n\twidth: '100%',\n\theight: '42px',\n\tpadding: '0 14px',\n\tmarginBottom: '6px',\n\tbackground: 'rgba(255,255,255,0.06)',\n\tborder: '1px solid var(--border)',\n\tborderRadius: '10px',\n\tcolor: 'var(--ink)',\n\tfontSize: '16px',\n\tfontFamily: 'inherit',\n\toutline: 'none',\n\tboxSizing: 'border-box',\n\t'&:focus': { borderColor: 'var(--accent)' },\n\t'&::placeholder': { color: 'var(--muted)' },\n})\n\nconst noResultsClass = css({\n\tpadding: '20px 14px',\n\ttextAlign: 'center',\n\tcolor: 'var(--muted)',\n\tfontSize: '14px',\n})\n\n// ── Default accessors ────────────────────────────────────────────────────\nconst defaultItemToValue = item=> item?.value ?? item\nconst defaultItemToLabel = item=> item?.label ?? String(item)\nconst defaultFilter = (item, query, itemToLabel)=> itemToLabel(item).toLowerCase().includes(query)\n\n// ── FilterableSelectMobile ───────────────────────────────────────────────\n//\n// A mobile bottom-sheet select with a filter input at the top of the list.\n// Wraps SelectMobile internally — exposes a flat single-component API.\n//\n// Props:\n// items data array (or getter)\n// value current value (string | getter)\n// onValueChange (value) => void\n// itemToValue (item) => string — default item.value ?? item\n// itemToLabel (item) => string — default item.label ?? String(item)\n// placeholder trigger placeholder text — default \"Select\"\n// filter (item, query, itemToLabel) => boolean — default substring match\n// clearable show a \"clear\" row\n// clearLabel label for the clear row\n// backdropClassName className for the backdrop\n// backdropStyle inline style for the backdrop\nconst FilterableSelectMobile = (props)=> {\n\tconst [local] = splitProps(props, [\n\t\t'items', 'value', 'onValueChange',\n\t\t'itemToValue', 'itemToLabel',\n\t\t'placeholder', 'filter',\n\t\t'clearable', 'clearLabel',\n\t\t'backdropClassName', 'backdropStyle',\n\t])\n\n\tconst itemToValue = local.itemToValue ?? defaultItemToValue\n\tconst itemToLabel = local.itemToLabel ?? defaultItemToLabel\n\tconst filter = local.filter ?? defaultFilter\n\n\tconst query = createSignal('')\n\tlet inputEl = null\n\n\t// Reactive filter — recomputes on typing and items changes.\n\tconst filteredItems = ()=> {\n\t\tconst q = query().trim().toLowerCase()\n\t\tconst list = typeof local.items === 'function' ? local.items() : (local.items ?? [])\n\t\treturn q ? list.filter(item=> filter(item, q, itemToLabel)) : list\n\t}\n\n\t// Auto-focus the input when the sheet opens; clear query on close.\n\tconst handleOpenChange = (isOpen)=> {\n\t\tif (isOpen){\n\t\t\tqueueMicrotask(()=> inputEl?.focus())\n\t\t} else {\n\t\t\tquery('')\n\t\t}\n\t}\n\n\treturn (\n\t\t<SelectMobile.Root\n\t\t\tvalue={local.value}\n\t\t\tonValueChange={local.onValueChange}\n\t\t\titems={local.items}\n\t\t\titemToValue={itemToValue}\n\t\t\titemToLabel={itemToLabel}\n\t\t\tonOpenChange={handleOpenChange}\n\t\t>\n\t\t\t<SelectMobile.Trigger placeholder={local.placeholder} />\n\t\t\t<SelectMobile.Content\n\t\t\t\tclearable={local.clearable}\n\t\t\t\tclearLabel={local.clearLabel}\n\t\t\t\tbackdropClassName={local.backdropClassName}\n\t\t\t\tbackdropStyle={local.backdropStyle}\n\t\t\t>\n\t\t\t\t<input\n\t\t\t\t\tref={el=> { inputEl = el }}\n\t\t\t\t\ttype='text'\n\t\t\t\t\tclassName={inputClass}\n\t\t\t\t\tplaceholder='Filter…'\n\t\t\t\t\tvalue={query}\n\t\t\t\t\tonInput={e=> query(e.target.value)}\n\t\t\t\t\tonClick={e=> e.stopPropagation()}\n\t\t\t\t\tonPointerDown={e=> {\n\t\t\t\t\t\t// iOS WebKit workaround: ensure focus moves to input even when\n\t\t\t\t\t\t// a previous button element holds focus (Chrome iOS bug).\n\t\t\t\t\t\te.target.focus({ preventScroll: true })\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t{()=> {\n\t\t\t\t\tconst list = filteredItems()\n\t\t\t\t\tif (list.length === 0){\n\t\t\t\t\t\treturn <div className={noResultsClass}>No results</div>\n\t\t\t\t\t}\n\t\t\t\t\treturn list.map(item => (\n\t\t\t\t\t\t<SelectMobile.Item item={item} key={itemToValue(item)} />\n\t\t\t\t\t))\n\t\t\t\t}}\n\t\t\t</SelectMobile.Content>\n\t\t</SelectMobile.Root>\n\t)\n}\n\nexport default FilterableSelectMobile"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anatomy.js","names":[],"sources":["../../../src/components/SelectMobile/anatomy.js"],"sourcesContent":["// Anatomy for SelectMobile — the named parts the component renders.\n// Every rendered element carries `data-scope=\"select-mobile\"` + `data-part=\"<name>\"`,\n// so consumers can target any part with CSS (e.g. [data-part=\"item\"][data-selected]).\nexport const SCOPE = 'select-mobile'\n\nexport const anatomy = [\n\t'trigger',\n\t'triggerValue',\n\t'triggerIndicator',\n\t'positioner',\n\t'backdrop',\n\t'content',\n\t'grabber',\n\t'list',\n\t'item',\n\t'itemText',\n\t'itemIndicator',\n]\n\n// Returns the data attributes for a given anatomy part.\nexport const part = name=> ({ 'data-scope': SCOPE, 'data-part': name })"],"mappings":";AAGA,IAAa,QAAQ;AAiBrB,IAAa,QAAO,UAAQ;CAAE,cAAc;CAAO,aAAa;AAAK"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import Icon from "../Icon.js";
|
|
2
|
+
import { part } from "./anatomy.js";
|
|
3
|
+
import { Fragment, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/jsx-runtime";
|
|
4
|
+
import { css } from "@emotion/css";
|
|
5
|
+
import { Loop, Portal, createContext, createEffect, createSignal, splitProps, useContext } from "@plastic-js/plastic";
|
|
6
|
+
//#region src/components/SelectMobile/index.jsx
|
|
7
|
+
var _tmpl = template("<span></span>");
|
|
8
|
+
var chevronDownSvg = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>";
|
|
9
|
+
var checkSvg = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6 9 17l-5-5\"/></svg>";
|
|
10
|
+
var triggerClass = css({
|
|
11
|
+
flexShrink: 0,
|
|
12
|
+
width: "100%",
|
|
13
|
+
height: "44px",
|
|
14
|
+
display: "flex",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
justifyContent: "space-between",
|
|
17
|
+
gap: "4px",
|
|
18
|
+
background: "var(--bg, rgba(0,0,0,0.2))",
|
|
19
|
+
border: "1px solid var(--border)",
|
|
20
|
+
borderRadius: "10px",
|
|
21
|
+
padding: "0 10px",
|
|
22
|
+
color: "var(--ink)",
|
|
23
|
+
fontSize: "15px",
|
|
24
|
+
fontFamily: "inherit",
|
|
25
|
+
textAlign: "left",
|
|
26
|
+
outline: "none",
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
"&:focus-visible": { borderColor: "var(--accent)" }
|
|
29
|
+
});
|
|
30
|
+
var triggerValueClass = css({
|
|
31
|
+
overflow: "hidden",
|
|
32
|
+
textOverflow: "ellipsis",
|
|
33
|
+
whiteSpace: "nowrap"
|
|
34
|
+
});
|
|
35
|
+
var triggerPlaceholderClass = css({ color: "var(--muted)" });
|
|
36
|
+
var triggerIndicatorClass = css({
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
color: "var(--muted)",
|
|
39
|
+
display: "flex",
|
|
40
|
+
"& svg": {
|
|
41
|
+
width: "16px",
|
|
42
|
+
height: "16px"
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
var overlayClass = css({
|
|
46
|
+
position: "fixed",
|
|
47
|
+
inset: 0,
|
|
48
|
+
zIndex: 1e3,
|
|
49
|
+
display: "flex",
|
|
50
|
+
flexDirection: "column",
|
|
51
|
+
justifyContent: "flex-end",
|
|
52
|
+
pointerEvents: "none"
|
|
53
|
+
});
|
|
54
|
+
var backdropClass = css({
|
|
55
|
+
position: "absolute",
|
|
56
|
+
inset: 0,
|
|
57
|
+
background: "rgba(0,0,0,0.5)",
|
|
58
|
+
opacity: 0,
|
|
59
|
+
transition: "opacity 240ms ease"
|
|
60
|
+
});
|
|
61
|
+
var sheetClass = css({
|
|
62
|
+
position: "relative",
|
|
63
|
+
width: "100%",
|
|
64
|
+
maxHeight: "70vh",
|
|
65
|
+
display: "flex",
|
|
66
|
+
flexDirection: "column",
|
|
67
|
+
background: "var(--surface)",
|
|
68
|
+
borderTop: "1px solid var(--border)",
|
|
69
|
+
borderRadius: "18px 18px 0 0",
|
|
70
|
+
boxShadow: "0 -8px 32px rgba(0,0,0,0.4)",
|
|
71
|
+
paddingBottom: "env(safe-area-inset-bottom, 0px)",
|
|
72
|
+
transform: "translateY(100%)",
|
|
73
|
+
transition: "transform 280ms cubic-bezier(0.32, 0.72, 0, 1)"
|
|
74
|
+
});
|
|
75
|
+
var openClass = css({
|
|
76
|
+
pointerEvents: "auto",
|
|
77
|
+
[`& .${backdropClass}`]: { opacity: 1 },
|
|
78
|
+
[`& .${sheetClass}`]: { transform: "translateY(0)" }
|
|
79
|
+
});
|
|
80
|
+
var grabberClass = css({
|
|
81
|
+
flexShrink: 0,
|
|
82
|
+
display: "flex",
|
|
83
|
+
justifyContent: "center",
|
|
84
|
+
padding: "10px 0 4px"
|
|
85
|
+
});
|
|
86
|
+
var grabberBarClass = css({
|
|
87
|
+
width: "36px",
|
|
88
|
+
height: "4px",
|
|
89
|
+
borderRadius: "999px",
|
|
90
|
+
background: "var(--border)"
|
|
91
|
+
});
|
|
92
|
+
var listClass = css({
|
|
93
|
+
overflowY: "auto",
|
|
94
|
+
WebkitOverflowScrolling: "touch",
|
|
95
|
+
padding: "0 8px 8px"
|
|
96
|
+
});
|
|
97
|
+
var itemClass = css({
|
|
98
|
+
width: "100%",
|
|
99
|
+
display: "flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
justifyContent: "space-between",
|
|
102
|
+
gap: "10px",
|
|
103
|
+
height: "52px",
|
|
104
|
+
padding: "0 14px",
|
|
105
|
+
background: "transparent",
|
|
106
|
+
border: "none",
|
|
107
|
+
borderRadius: "12px",
|
|
108
|
+
color: "var(--ink)",
|
|
109
|
+
fontSize: "16px",
|
|
110
|
+
fontFamily: "inherit",
|
|
111
|
+
textAlign: "left",
|
|
112
|
+
cursor: "pointer",
|
|
113
|
+
"&:active": { background: "rgba(255,255,255,0.06)" }
|
|
114
|
+
});
|
|
115
|
+
var itemIndicatorClass = css({
|
|
116
|
+
flexShrink: 0,
|
|
117
|
+
color: "var(--accent)",
|
|
118
|
+
display: "flex",
|
|
119
|
+
"& svg": {
|
|
120
|
+
width: "18px",
|
|
121
|
+
height: "18px"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
var SelectMobileContext = createContext(null);
|
|
125
|
+
var useSelectMobile = () => {
|
|
126
|
+
const ctx = useContext(SelectMobileContext);
|
|
127
|
+
if (!ctx) throw new Error("SelectMobile parts must be used inside <SelectMobile.Root>");
|
|
128
|
+
return ctx;
|
|
129
|
+
};
|
|
130
|
+
var read = (source) => {
|
|
131
|
+
return typeof source === "function" ? source() : source;
|
|
132
|
+
};
|
|
133
|
+
var Root = (props) => {
|
|
134
|
+
const [local, rest] = splitProps(props, [
|
|
135
|
+
"value",
|
|
136
|
+
"onValueChange",
|
|
137
|
+
"items",
|
|
138
|
+
"itemToValue",
|
|
139
|
+
"itemToLabel",
|
|
140
|
+
"open",
|
|
141
|
+
"defaultOpen",
|
|
142
|
+
"onOpenChange",
|
|
143
|
+
"children"
|
|
144
|
+
]);
|
|
145
|
+
const internalOpen = createSignal(local.defaultOpen ?? false);
|
|
146
|
+
const open = (v) => {
|
|
147
|
+
if (v === void 0) return local.open !== void 0 ? read(local.open) : internalOpen();
|
|
148
|
+
if (local.open !== void 0) local.onOpenChange?.(v);
|
|
149
|
+
else {
|
|
150
|
+
internalOpen(v);
|
|
151
|
+
local.onOpenChange?.(v);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const itemToValue = (item) => {
|
|
155
|
+
if (item == null) return;
|
|
156
|
+
return local.itemToValue ? local.itemToValue(item) : item.value;
|
|
157
|
+
};
|
|
158
|
+
const itemToLabel = (item) => {
|
|
159
|
+
if (item == null) return;
|
|
160
|
+
return local.itemToLabel ? local.itemToLabel(item) : item.label;
|
|
161
|
+
};
|
|
162
|
+
const items = () => read(local.items) || [];
|
|
163
|
+
const value = () => read(local.value) ?? "";
|
|
164
|
+
const isSelected = (v) => String(value()) === String(v);
|
|
165
|
+
const selectedItem = () => items().find((item) => isSelected(itemToValue(item)));
|
|
166
|
+
const setValue = (v) => {
|
|
167
|
+
local.onValueChange?.(v);
|
|
168
|
+
open(false);
|
|
169
|
+
};
|
|
170
|
+
createEffect(() => {
|
|
171
|
+
if (!open()) return;
|
|
172
|
+
const onKey = (e) => {
|
|
173
|
+
if (e.key === "Escape") open(false);
|
|
174
|
+
};
|
|
175
|
+
document.addEventListener("keydown", onKey);
|
|
176
|
+
const prevOverflow = document.body.style.overflow;
|
|
177
|
+
document.body.style.overflow = "hidden";
|
|
178
|
+
return () => {
|
|
179
|
+
document.removeEventListener("keydown", onKey);
|
|
180
|
+
document.body.style.overflow = prevOverflow;
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
const ctx = {
|
|
184
|
+
open,
|
|
185
|
+
value,
|
|
186
|
+
items,
|
|
187
|
+
itemToValue,
|
|
188
|
+
itemToLabel,
|
|
189
|
+
isSelected,
|
|
190
|
+
selectedItem,
|
|
191
|
+
setValue
|
|
192
|
+
};
|
|
193
|
+
return jsx(SelectMobileContext.Provider, mergeProps({
|
|
194
|
+
value: ctx,
|
|
195
|
+
children: () => jsx("div", mergeProps(() => part("root"), rest, { children: () => local.children }))
|
|
196
|
+
}));
|
|
197
|
+
};
|
|
198
|
+
var Trigger = (props) => {
|
|
199
|
+
const ctx = useSelectMobile();
|
|
200
|
+
const [local, rest] = splitProps(props, [
|
|
201
|
+
"placeholder",
|
|
202
|
+
"className",
|
|
203
|
+
"children"
|
|
204
|
+
]);
|
|
205
|
+
return jsx("div", mergeProps(() => part("trigger"), {
|
|
206
|
+
get className() {
|
|
207
|
+
return `${triggerClass} ${local.className || ""}`;
|
|
208
|
+
},
|
|
209
|
+
onClick: () => ctx.open(true),
|
|
210
|
+
role: "button",
|
|
211
|
+
tabIndex: 0
|
|
212
|
+
}, rest, { children: () => local.children ?? jsx(Fragment, { children: [jsx("span", mergeProps(() => part("triggerValue"), {
|
|
213
|
+
get className() {
|
|
214
|
+
return `${triggerValueClass} ${ctx.selectedItem() ? "" : triggerPlaceholderClass}`;
|
|
215
|
+
},
|
|
216
|
+
children: () => {
|
|
217
|
+
const item = ctx.selectedItem();
|
|
218
|
+
return item !== void 0 ? ctx.itemToLabel(item) : local.placeholder ?? "Select";
|
|
219
|
+
}
|
|
220
|
+
})), jsx("span", mergeProps(() => part("triggerIndicator"), {
|
|
221
|
+
className: triggerIndicatorClass,
|
|
222
|
+
children: jsx(Icon, mergeProps({ svg: chevronDownSvg }))
|
|
223
|
+
}))] }) }));
|
|
224
|
+
};
|
|
225
|
+
var Item = (props) => {
|
|
226
|
+
const ctx = useSelectMobile();
|
|
227
|
+
const [local, rest] = splitProps(props, [
|
|
228
|
+
"item",
|
|
229
|
+
"value",
|
|
230
|
+
"className",
|
|
231
|
+
"children"
|
|
232
|
+
]);
|
|
233
|
+
const value = () => local.item !== void 0 ? ctx.itemToValue(local.item) : local.value;
|
|
234
|
+
return jsx("button", mergeProps(() => part("item"), {
|
|
235
|
+
get className() {
|
|
236
|
+
return `${itemClass} ${local.className || ""}`;
|
|
237
|
+
},
|
|
238
|
+
"data-selected": () => ctx.isSelected(value()) ? "" : void 0,
|
|
239
|
+
onClick: () => ctx.setValue(value()),
|
|
240
|
+
type: "button"
|
|
241
|
+
}, rest, { children: [jsx("span", mergeProps(() => part("itemText"), { children: () => local.children ?? (local.item !== void 0 && ctx.itemToLabel(local.item)) })), () => ctx.isSelected(value()) && jsx("span", mergeProps(() => part("itemIndicator"), {
|
|
242
|
+
className: itemIndicatorClass,
|
|
243
|
+
children: jsx(Icon, mergeProps({ svg: checkSvg }))
|
|
244
|
+
}))] }));
|
|
245
|
+
};
|
|
246
|
+
var Content = (props) => {
|
|
247
|
+
const ctx = useSelectMobile();
|
|
248
|
+
const [local, rest] = splitProps(props, [
|
|
249
|
+
"clearable",
|
|
250
|
+
"clearLabel",
|
|
251
|
+
"className",
|
|
252
|
+
"backdropClassName",
|
|
253
|
+
"backdropStyle",
|
|
254
|
+
"children"
|
|
255
|
+
]);
|
|
256
|
+
return jsx(Portal, mergeProps({ children: jsx("div", mergeProps(() => part("positioner"), {
|
|
257
|
+
"aria-hidden": () => !ctx.open(),
|
|
258
|
+
get className() {
|
|
259
|
+
return `${overlayClass} ${ctx.open() ? openClass : ""}`;
|
|
260
|
+
},
|
|
261
|
+
children: [jsx("div", mergeProps(() => part("backdrop"), {
|
|
262
|
+
get className() {
|
|
263
|
+
return `${backdropClass} ${local.backdropClassName || ""}`;
|
|
264
|
+
},
|
|
265
|
+
get style() {
|
|
266
|
+
return local.backdropStyle;
|
|
267
|
+
},
|
|
268
|
+
onClick: () => ctx.open(false)
|
|
269
|
+
})), jsx("div", mergeProps(() => part("content"), {
|
|
270
|
+
get className() {
|
|
271
|
+
return `${sheetClass} ${local.className || ""}`;
|
|
272
|
+
},
|
|
273
|
+
role: "dialog"
|
|
274
|
+
}, rest, { children: [jsx("div", mergeProps(() => part("grabber"), {
|
|
275
|
+
className: grabberClass,
|
|
276
|
+
onClick: () => ctx.open(false),
|
|
277
|
+
children: (() => {
|
|
278
|
+
const _el0 = _tmpl.cloneNode(true);
|
|
279
|
+
setProp(_el0, "className", () => grabberBarClass);
|
|
280
|
+
return _el0;
|
|
281
|
+
})()
|
|
282
|
+
})), jsx("div", mergeProps(() => part("list"), {
|
|
283
|
+
className: listClass,
|
|
284
|
+
children: () => local.children ?? jsx(Fragment, { children: [() => local.clearable && jsx(Item, mergeProps({
|
|
285
|
+
value: "",
|
|
286
|
+
children: () => local.clearLabel ?? "None"
|
|
287
|
+
})), jsx(Loop, mergeProps({
|
|
288
|
+
get each() {
|
|
289
|
+
return ctx.items;
|
|
290
|
+
},
|
|
291
|
+
children: (item) => jsx(Item, mergeProps({
|
|
292
|
+
item,
|
|
293
|
+
get key() {
|
|
294
|
+
return ctx.itemToValue(item);
|
|
295
|
+
}
|
|
296
|
+
}))
|
|
297
|
+
}))] })
|
|
298
|
+
}))] }))]
|
|
299
|
+
})) }));
|
|
300
|
+
};
|
|
301
|
+
var SelectMobile = Object.assign(Root, {
|
|
302
|
+
Root,
|
|
303
|
+
Trigger,
|
|
304
|
+
Content,
|
|
305
|
+
Item
|
|
306
|
+
});
|
|
307
|
+
//#endregion
|
|
308
|
+
export { SelectMobile as default };
|
|
309
|
+
|
|
310
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["css","Loop","Portal","createContext","createEffect","createSignal","splitProps","useContext","Icon","part","_tmpl","_template","chevronDownSvg","checkSvg","triggerClass","flexShrink","width","height","display","alignItems","justifyContent","gap","background","border","borderRadius","padding","color","fontSize","fontFamily","textAlign","outline","cursor","borderColor","triggerValueClass","overflow","textOverflow","whiteSpace","triggerPlaceholderClass","triggerIndicatorClass","overlayClass","position","inset","zIndex","flexDirection","pointerEvents","backdropClass","opacity","transition","sheetClass","maxHeight","borderTop","boxShadow","paddingBottom","transform","openClass","grabberClass","grabberBarClass","listClass","overflowY","WebkitOverflowScrolling","itemClass","itemIndicatorClass","SelectMobileContext","useSelectMobile","ctx","Error","read","source","Root","props","local","rest","internalOpen","defaultOpen","open","v","undefined","onOpenChange","itemToValue","item","value","itemToLabel","label","items","isSelected","String","selectedItem","find","setValue","onValueChange","onKey","e","key","document","addEventListener","prevOverflow","body","style","removeEventListener","_jsx","Provider","_mergeProps","children","Trigger","className","onClick","role","tabIndex","_Fragment","placeholder","svg","Item","data-selected","type","Content","aria-hidden","backdropClassName","backdropStyle","_el0","cloneNode","_setProp","clearable","clearLabel","each","SelectMobile","Object","assign"],"sources":["../../../src/components/SelectMobile/index.jsx"],"sourcesContent":["import { css } from '@emotion/css'\nimport {\n\tLoop, Portal, createContext, createEffect, createSignal, splitProps, useContext,\n} from '@plastic-js/plastic'\nimport Icon from '../Icon.jsx'\nimport { part } from './anatomy.js'\n\nconst chevronDownSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>'\nconst checkSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6 9 17l-5-5\"/></svg>'\n\nconst triggerClass = css({\n\tflexShrink: 0,\n\twidth: '100%',\n\theight: '44px',\n\tdisplay: 'flex',\n\talignItems: 'center',\n\tjustifyContent: 'space-between',\n\tgap: '4px',\n\tbackground: 'var(--bg, rgba(0,0,0,0.2))',\n\tborder: '1px solid var(--border)',\n\tborderRadius: '10px',\n\tpadding: '0 10px',\n\tcolor: 'var(--ink)',\n\tfontSize: '15px',\n\tfontFamily: 'inherit',\n\ttextAlign: 'left',\n\toutline: 'none',\n\tcursor: 'pointer',\n\t'&:focus-visible': { borderColor: 'var(--accent)' },\n})\n\nconst triggerValueClass = css({\n\toverflow: 'hidden',\n\ttextOverflow: 'ellipsis',\n\twhiteSpace: 'nowrap',\n})\n\nconst triggerPlaceholderClass = css({\n\tcolor: 'var(--muted)',\n})\n\nconst triggerIndicatorClass = css({\n\tflexShrink: 0,\n\tcolor: 'var(--muted)',\n\tdisplay: 'flex',\n\t'& svg': { width: '16px', height: '16px' },\n})\n\nconst overlayClass = css({\n\tposition: 'fixed',\n\tinset: 0,\n\tzIndex: 1000,\n\tdisplay: 'flex',\n\tflexDirection: 'column',\n\tjustifyContent: 'flex-end',\n\tpointerEvents: 'none',\n})\n\nconst backdropClass = css({\n\tposition: 'absolute',\n\tinset: 0,\n\tbackground: 'rgba(0,0,0,0.5)',\n\topacity: 0,\n\ttransition: 'opacity 240ms ease',\n})\n\nconst sheetClass = css({\n\tposition: 'relative',\n\twidth: '100%',\n\tmaxHeight: '70vh',\n\tdisplay: 'flex',\n\tflexDirection: 'column',\n\tbackground: 'var(--surface)',\n\tborderTop: '1px solid var(--border)',\n\tborderRadius: '18px 18px 0 0',\n\tboxShadow: '0 -8px 32px rgba(0,0,0,0.4)',\n\tpaddingBottom: 'env(safe-area-inset-bottom, 0px)',\n\ttransform: 'translateY(100%)',\n\ttransition: 'transform 280ms cubic-bezier(0.32, 0.72, 0, 1)',\n})\n\nconst openClass = css({\n\tpointerEvents: 'auto',\n\t[`& .${backdropClass}`]: { opacity: 1 },\n\t[`& .${sheetClass}`]: { transform: 'translateY(0)' },\n})\n\nconst grabberClass = css({\n\tflexShrink: 0,\n\tdisplay: 'flex',\n\tjustifyContent: 'center',\n\tpadding: '10px 0 4px',\n})\n\nconst grabberBarClass = css({\n\twidth: '36px',\n\theight: '4px',\n\tborderRadius: '999px',\n\tbackground: 'var(--border)',\n})\n\nconst listClass = css({\n\toverflowY: 'auto',\n\tWebkitOverflowScrolling: 'touch',\n\tpadding: '0 8px 8px',\n})\n\nconst itemClass = css({\n\twidth: '100%',\n\tdisplay: 'flex',\n\talignItems: 'center',\n\tjustifyContent: 'space-between',\n\tgap: '10px',\n\theight: '52px',\n\tpadding: '0 14px',\n\tbackground: 'transparent',\n\tborder: 'none',\n\tborderRadius: '12px',\n\tcolor: 'var(--ink)',\n\tfontSize: '16px',\n\tfontFamily: 'inherit',\n\ttextAlign: 'left',\n\tcursor: 'pointer',\n\t'&:active': { background: 'rgba(255,255,255,0.06)' },\n})\n\nconst itemIndicatorClass = css({\n\tflexShrink: 0,\n\tcolor: 'var(--accent)',\n\tdisplay: 'flex',\n\t'& svg': { width: '18px', height: '18px' },\n})\n\nconst SelectMobileContext = createContext(null)\n\nconst useSelectMobile = ()=> {\n\tconst ctx = useContext(SelectMobileContext)\n\tif (!ctx){ throw new Error('SelectMobile parts must be used inside <SelectMobile.Root>') }\n\treturn ctx\n}\n\nconst read = (source)=> {\n\treturn typeof source === 'function' ? source() : source\n}\n\n// <SelectMobile.Root> — holds selection + open state, resolves item -> value/label.\n// Props:\n// value current value (string | getter)\n// onValueChange (value) => void — called when an item is chosen\n// items data array (or getter) supplied by the consumer\n// itemToValue (item) => string — default item.value\n// itemToLabel (item) => node — default item.label\n// open controlled open state (getter) — when provided, the component\n// is controlled and you must update it via onOpenChange\n// defaultOpen initial open state for uncontrolled mode (default false)\n// onOpenChange (isOpen) => void — called when open state changes\nconst Root = (props)=> {\n\tconst [local, rest] = splitProps(props, [\n\t\t'value', 'onValueChange', 'items', 'itemToValue', 'itemToLabel',\n\t\t'open', 'defaultOpen', 'onOpenChange', 'children',\n\t])\n\tconst internalOpen = createSignal(local.defaultOpen ?? false)\n\n\tconst open = (v)=> {\n\t\tif (v === undefined){\n\t\t\t// getter — controlled if `open` prop is provided, else internal\n\t\t\treturn local.open !== undefined ? read(local.open) : internalOpen()\n\t\t}\n\t\t// setter\n\t\tif (local.open !== undefined){\n\t\t\tlocal.onOpenChange?.(v)\n\t\t} else {\n\t\t\tinternalOpen(v)\n\t\t\tlocal.onOpenChange?.(v)\n\t\t}\n\t}\n\n\tconst itemToValue = (item)=> {\n\t\tif (item == null){ return undefined }\n\t\treturn local.itemToValue ? local.itemToValue(item) : item.value\n\t}\n\tconst itemToLabel = (item)=> {\n\t\tif (item == null){ return undefined }\n\t\treturn local.itemToLabel ? local.itemToLabel(item) : item.label\n\t}\n\tconst items = ()=> read(local.items) || []\n\tconst value = ()=> read(local.value) ?? ''\n\tconst isSelected = v=> String(value()) === String(v)\n\tconst selectedItem = ()=> items().find(item=> isSelected(itemToValue(item)))\n\n\tconst setValue = (v)=> {\n\t\tlocal.onValueChange?.(v)\n\t\topen(false)\n\t}\n\n\tcreateEffect(()=> {\n\t\tif (!open()){ return undefined }\n\t\tconst onKey = (e)=> { if (e.key === 'Escape'){ open(false) } }\n\t\tdocument.addEventListener('keydown', onKey)\n\t\tconst prevOverflow = document.body.style.overflow\n\t\tdocument.body.style.overflow = 'hidden'\n\t\treturn ()=> {\n\t\t\tdocument.removeEventListener('keydown', onKey)\n\t\t\tdocument.body.style.overflow = prevOverflow\n\t\t}\n\t})\n\n\t// Plastic components run once (fine-grained reactivity), so this object is\n\t// created a single time per mount — the React \"stable value\" rule misfires.\n\t// eslint-disable-next-line react/jsx-no-constructed-context-values\n\tconst ctx = {\n\t\topen, value, items, itemToValue, itemToLabel, isSelected, selectedItem, setValue,\n\t}\n\n\treturn (\n\t\t<SelectMobileContext.Provider value={ctx}>\n\t\t\t<div {...part('root')} {...rest}>\n\t\t\t\t{local.children}\n\t\t\t</div>\n\t\t</SelectMobileContext.Provider>\n\t)\n}\n\n// <SelectMobile.Trigger> — the button that opens the sheet. Shows the selected\n// item's label (or `placeholder`). Pass children to fully customize.\n//\n// NOTE: Uses <div role=\"button\"> instead of a native <button> to work around a\n// Chrome iOS (WebKit) bug where tapping a <button> inside a scrollable container\n// with -webkit-overflow-scrolling:touch permanently pins activeElement to the\n// button. After the trigger opens the sheet, any subsequent focus() call on the\n// search <input> inside the list is silently ignored — the browser refuses to\n// release focus from the button. A <div role=\"button\"> is semantically equivalent\n// for accessibility but doesn't trigger this WebKit focus-lock behaviour.\n//\n// The original <button> was chosen for keyboard tab accessibility (tabIndex\n// works natively on <button>). On mobile, however, keyboard navigation is\n// irrelevant — the sheet is operated via touch, and the Escape key for closing\n// is handled by the Dialog overlay, not by tab order. The <div role=\"button\">\n// with tabIndex={0} preserves keyboard discoverability for assistive technology\n// while avoiding the iOS focus trap.\nconst Trigger = (props)=> {\n\tconst ctx = useSelectMobile()\n\tconst [local, rest] = splitProps(props, ['placeholder', 'className', 'children'])\n\n\treturn (\n\t\t<div\n\t\t\t{...part('trigger')}\n\t\t\tclassName={`${triggerClass} ${local.className || ''}`}\n\t\t\tonClick={()=> ctx.open(true)}\n\t\t\trole='button'\n\t\t\ttabIndex={0}\n\t\t\t{...rest}\n\t\t>\n\t\t\t{local.children ?? (\n\t\t\t\t<>\n\t\t\t\t\t<span\n\t\t\t\t\t\t{...part('triggerValue')}\n\t\t\t\t\t\tclassName={`${triggerValueClass} ${ctx.selectedItem() ? '' : triggerPlaceholderClass}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{()=> {\n\t\t\t\t\t\t\tconst item = ctx.selectedItem()\n\t\t\t\t\t\t\treturn item !== undefined ? ctx.itemToLabel(item) : local.placeholder ?? 'Select'\n\t\t\t\t\t\t}}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span {...part('triggerIndicator')} className={triggerIndicatorClass}>\n\t\t\t\t\t\t<Icon svg={chevronDownSvg} />\n\t\t\t\t\t</span>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n\n// <SelectMobile.Item> — a selectable row. Provide `item` (resolved via Root's\n// accessors) or an explicit `value` + children for custom content.\nconst Item = (props)=> {\n\tconst ctx = useSelectMobile()\n\tconst [local, rest] = splitProps(props, ['item', 'value', 'className', 'children'])\n\tconst value = ()=> local.item !== undefined ? ctx.itemToValue(local.item) : local.value\n\n\treturn (\n\t\t<button\n\t\t\t{...part('item')}\n\t\t\tclassName={`${itemClass} ${local.className || ''}`}\n\t\t\tdata-selected={()=> ctx.isSelected(value()) ? '' : undefined}\n\t\t\tonClick={()=> ctx.setValue(value())}\n\t\t\ttype='button'\n\t\t\t{...rest}\n\t\t>\n\t\t\t<span {...part('itemText')}>\n\t\t\t\t{local.children ?? (local.item !== undefined && ctx.itemToLabel(local.item))}\n\t\t\t</span>\n\t\t\t{()=> ctx.isSelected(value()) && (\n\t\t\t\t<span {...part('itemIndicator')} className={itemIndicatorClass}>\n\t\t\t\t\t<Icon svg={checkSvg} />\n\t\t\t\t</span>\n\t\t\t)}\n\t\t</button>\n\t)\n}\n\n// <SelectMobile.Content> — the bottom sheet. With no children it auto-renders one\n// SelectMobile.Item per Root item; pass `clearable` to prepend a \"clear\" row.\n// Props:\n// clearable show a \"clear\" row at the top\n// clearLabel label for the clear row (default \"None\")\n// backdropClassName className passed to the backdrop (higher priority)\n// backdropStyle inline style passed to the backdrop (higher priority)\nconst Content = (props)=> {\n\tconst ctx = useSelectMobile()\n\tconst [local, rest] = splitProps(props, [\n\t\t'clearable', 'clearLabel', 'className',\n\t\t'backdropClassName', 'backdropStyle', 'children',\n\t])\n\n\treturn (\n\t\t<Portal>\n\t\t\t<div\n\t\t\t\t{...part('positioner')}\n\t\t\t\taria-hidden={()=> !ctx.open()}\n\t\t\t\tclassName={`${overlayClass} ${ctx.open() ? openClass : ''}`}\n\t\t\t>\n\t\t\t\t<div {...part('backdrop')} className={`${backdropClass} ${local.backdropClassName || ''}`} style={local.backdropStyle} onClick={()=> ctx.open(false)} />\n\t\t\t\t<div {...part('content')} className={`${sheetClass} ${local.className || ''}`} role='dialog' {...rest}>\n\t\t\t\t\t<div {...part('grabber')} className={grabberClass} onClick={()=> ctx.open(false)}>\n\t\t\t\t\t\t<span className={grabberBarClass} />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div {...part('list')} className={listClass}>\n\t\t\t\t\t\t{local.children ?? (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t{()=> local.clearable && (\n\t\t\t\t\t\t\t\t\t<Item value=''>\n\t\t\t\t\t\t\t\t\t\t{local.clearLabel ?? 'None'}\n\t\t\t\t\t\t\t\t\t</Item>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t<Loop each={ctx.items}>\n\t\t\t\t\t\t\t\t\t{item=> <Item item={item} key={ctx.itemToValue(item)} />}\n\t\t\t\t\t\t\t\t</Loop>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Portal>\n\t)\n}\n\nconst SelectMobile = Object.assign(Root, {\n\tRoot, Trigger, Content, Item,\n})\n\nexport default SelectMobile\nexport {\n\tSelectMobile, Root, Trigger, Content, Item,\n}"],"mappings":"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"}
|
|
@@ -5,6 +5,8 @@ import { createEffect, createSignal } from "@plastic-js/plastic";
|
|
|
5
5
|
var _tmpl2 = template("<button type=\"button\"></button>");
|
|
6
6
|
var _tmpl = template("<div><div data-dragging=\"false\"><div></div><div></div></div></div>");
|
|
7
7
|
var ACTIONS_WIDTH = 160;
|
|
8
|
+
var VELOCITY_THRESHOLD = .5;
|
|
9
|
+
var RUBBER_BAND_LIMIT = 50;
|
|
8
10
|
var wrapperClass = css`
|
|
9
11
|
position: relative;
|
|
10
12
|
overflow: hidden;
|
|
@@ -15,7 +17,7 @@ var trackClass = css`
|
|
|
15
17
|
display: flex;
|
|
16
18
|
align-items: stretch;
|
|
17
19
|
will-change: transform;
|
|
18
|
-
transition: transform
|
|
20
|
+
transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
19
21
|
touch-action: pan-y;
|
|
20
22
|
&[data-dragging="true"] { transition: none; }
|
|
21
23
|
`;
|
|
@@ -43,8 +45,11 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
43
45
|
let trackEl = null;
|
|
44
46
|
let startX = 0;
|
|
45
47
|
let startY = 0;
|
|
48
|
+
let lastX = 0;
|
|
49
|
+
let lastTime = 0;
|
|
46
50
|
let baseOffset = 0;
|
|
47
51
|
let currentOffset = 0;
|
|
52
|
+
let velocity = 0;
|
|
48
53
|
let dragging = false;
|
|
49
54
|
let axisLocked = false;
|
|
50
55
|
let horizontal = false;
|
|
@@ -55,12 +60,13 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
55
60
|
const applyTransform = (px) => {
|
|
56
61
|
if (trackEl) trackEl.style.transform = `translateX(${px}px)`;
|
|
57
62
|
};
|
|
63
|
+
const setDragging = (v) => {
|
|
64
|
+
if (trackEl) trackEl.dataset.dragging = String(v);
|
|
65
|
+
};
|
|
58
66
|
const snap = (toOpen) => {
|
|
59
67
|
currentOffset = toOpen ? -160 : 0;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
applyTransform(currentOffset);
|
|
63
|
-
}
|
|
68
|
+
setDragging(false);
|
|
69
|
+
applyTransform(currentOffset);
|
|
64
70
|
onOpenChange?.(toOpen);
|
|
65
71
|
isOpen(toOpen);
|
|
66
72
|
};
|
|
@@ -69,21 +75,23 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
69
75
|
if (currentActive !== void 0 && currentActive !== thisId && isOpen()) {
|
|
70
76
|
isOpen(false);
|
|
71
77
|
currentOffset = 0;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
applyTransform(0);
|
|
75
|
-
}
|
|
78
|
+
setDragging(false);
|
|
79
|
+
applyTransform(0);
|
|
76
80
|
}
|
|
77
81
|
});
|
|
78
82
|
const onPointerDown = (e) => {
|
|
79
83
|
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
80
84
|
startX = e.clientX;
|
|
81
85
|
startY = e.clientY;
|
|
86
|
+
lastX = e.clientX;
|
|
87
|
+
lastTime = performance.now();
|
|
82
88
|
baseOffset = isOpen() ? -160 : 0;
|
|
89
|
+
velocity = 0;
|
|
83
90
|
dragging = true;
|
|
84
91
|
axisLocked = false;
|
|
85
92
|
horizontal = false;
|
|
86
93
|
moved = false;
|
|
94
|
+
onSwipeStart?.();
|
|
87
95
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
88
96
|
};
|
|
89
97
|
const onPointerMove = (e) => {
|
|
@@ -94,13 +102,21 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
94
102
|
if (Math.abs(dx) < 6 && Math.abs(dy) < 6) return;
|
|
95
103
|
horizontal = Math.abs(dx) > Math.abs(dy);
|
|
96
104
|
axisLocked = true;
|
|
97
|
-
if (horizontal
|
|
105
|
+
if (horizontal) setDragging(true);
|
|
98
106
|
}
|
|
99
107
|
if (!horizontal) return;
|
|
100
108
|
moved = true;
|
|
109
|
+
const now = performance.now();
|
|
110
|
+
const dt = now - lastTime;
|
|
111
|
+
if (dt > 0) velocity = (e.clientX - lastX) / dt;
|
|
112
|
+
lastX = e.clientX;
|
|
113
|
+
lastTime = now;
|
|
101
114
|
let next = baseOffset + dx;
|
|
102
|
-
if (next > 0) next =
|
|
103
|
-
if (next < -160)
|
|
115
|
+
if (next > 0) next = RUBBER_BAND_LIMIT * (1 - Math.exp(-next / RUBBER_BAND_LIMIT));
|
|
116
|
+
if (next < -160) {
|
|
117
|
+
const over = -(next + ACTIONS_WIDTH);
|
|
118
|
+
next = -160 - RUBBER_BAND_LIMIT * (1 - Math.exp(-over / RUBBER_BAND_LIMIT));
|
|
119
|
+
}
|
|
104
120
|
currentOffset = next;
|
|
105
121
|
applyTransform(next);
|
|
106
122
|
};
|
|
@@ -109,6 +125,10 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
109
125
|
dragging = false;
|
|
110
126
|
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
111
127
|
if (!horizontal) return;
|
|
128
|
+
if (Math.abs(velocity) > VELOCITY_THRESHOLD) {
|
|
129
|
+
snap(velocity < 0);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
112
132
|
snap(currentOffset < -80);
|
|
113
133
|
};
|
|
114
134
|
const handleContentClick = (e) => {
|
|
@@ -135,10 +155,11 @@ var SwipeReveal = ({ actions, children, onSwipeStart, onOpenChange, activeId, th
|
|
|
135
155
|
insert(_el2, () => children);
|
|
136
156
|
setProp(_el2, "className", () => contentClass);
|
|
137
157
|
setProp(_el2, "onClick", () => handleContentClick);
|
|
138
|
-
insert(_el3, () => actions.map((action) => () => {
|
|
158
|
+
insert(_el3, () => actions.map((action, idx) => () => {
|
|
139
159
|
const _el0 = _tmpl2.cloneNode(true);
|
|
140
160
|
insert(_el0, () => action.label);
|
|
141
161
|
setProp(_el0, "className", () => actionBtnClass);
|
|
162
|
+
setProp(_el0, "key", () => action.label ?? idx);
|
|
142
163
|
setProp(_el0, "onClick", () => handleActionClick(action));
|
|
143
164
|
setProp(_el0, "style", () => ({ background: action.color }));
|
|
144
165
|
setProp(_el0, "tabIndex", () => isOpen() ? 0 : -1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SwipeReveal.js","names":["css","createEffect","createSignal","_tmpl2","_template","_tmpl","ACTIONS_WIDTH","SWIPE_THRESHOLD","wrapperClass","trackClass","actionsClass","actionBtnClass","contentClass","SwipeReveal","actions","children","onSwipeStart","onOpenChange","activeId","thisId","isOpen","trackEl","startX","startY","baseOffset","currentOffset","dragging","axisLocked","horizontal","moved","setTrackRef","el","applyTransform","px","style","transform","snap","toOpen","dataset","currentActive","undefined","onPointerDown","e","pointerType","button","clientX","clientY","currentTarget","setPointerCapture","pointerId","onPointerMove","dx","dy","Math","abs","next","onPointerUp","releasePointerCapture","shouldOpen","handleContentClick","stopPropagation","handleActionClick","action","onClick","_el0","cloneNode","_el1","firstChild","_el2","_el3","nextSibling","_insert","_setProp","map","label","background","color"],"sources":["../../src/components/SwipeReveal.jsx"],"sourcesContent":["import { css } from '@emotion/css'\nimport { createEffect, createSignal } from '@plastic-js/plastic'\n\nconst ACTIONS_WIDTH = 160\nconst SWIPE_THRESHOLD = ACTIONS_WIDTH / 2\n\nconst wrapperClass = css`\n\tposition: relative;\n\toverflow: hidden;\n\tborder-radius: 12px;\n\tuser-select: none;\n`\n\nconst trackClass = css`\n\tdisplay: flex;\n\talign-items: stretch;\n\twill-change: transform;\n\ttransition: transform 220ms ease;\n\ttouch-action: pan-y;\n\t&[data-dragging=\"true\"] { transition: none; }\n`\n\nconst actionsClass = css`\n\tflex: 0 0 ${ACTIONS_WIDTH}px;\n\tdisplay: flex;\n`\n\nconst actionBtnClass = css`\n\tflex: 1;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder: none;\n\tcolor: #fff;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcursor: pointer;\n\tfont-family: inherit;\n`\n\nconst contentClass = css`\n\tflex: 0 0 100%;\n`\n\nconst SwipeReveal = ({\n\tactions, children, onSwipeStart, onOpenChange, activeId, thisId,\n})=> {\n\tconst isOpen = createSignal(false)\n\n\tlet trackEl = null\n\tlet startX = 0\n\tlet startY = 0\n\tlet baseOffset = 0\n\tlet currentOffset = 0\n\tlet dragging = false\n\tlet axisLocked = false\n\tlet horizontal = false\n\tlet moved = false\n\n\tconst setTrackRef = (el)=> { trackEl = el }\n\n\tconst applyTransform = (px)=> {\n\t\tif (trackEl){ trackEl.style.transform = `translateX(${px}px)` }\n\t}\n\n\tconst snap = (toOpen)=> {\n\t\tcurrentOffset = toOpen ? -ACTIONS_WIDTH : 0\n\t\tif (trackEl){\n\t\t\ttrackEl.dataset.dragging = 'false'\n\t\t\tapplyTransform(currentOffset)\n\t\t}\n\t\tonOpenChange?.(toOpen)\n\t\tisOpen(toOpen)\n\t}\n\n\t// only one card open at a time — close when another card's swipe activates\n\tcreateEffect(()=> {\n\t\tconst currentActive = typeof activeId === 'function' ? activeId() : activeId\n\t\tif (currentActive !== undefined && currentActive !== thisId && isOpen()){\n\t\t\tisOpen(false)\n\t\t\tcurrentOffset = 0\n\t\t\tif (trackEl){\n\t\t\t\ttrackEl.dataset.dragging = 'false'\n\t\t\t\tapplyTransform(0)\n\t\t\t}\n\t\t}\n\t})\n\n\tconst onPointerDown = (e)=> {\n\t\tif (e.pointerType === 'mouse' && e.button !== 0){ return }\n\t\tstartX = e.clientX\n\t\tstartY = e.clientY\n\t\tbaseOffset = isOpen() ? -ACTIONS_WIDTH : 0\n\t\tdragging = true\n\t\taxisLocked = false\n\t\thorizontal = false\n\t\tmoved = false\n\t\te.currentTarget.setPointerCapture?.(e.pointerId)\n\t}\n\n\tconst onPointerMove = (e)=> {\n\t\tif (!dragging){ return }\n\t\tconst dx = e.clientX - startX\n\t\tconst dy = e.clientY - startY\n\t\tif (!axisLocked){\n\t\t\tif (Math.abs(dx) < 6 && Math.abs(dy) < 6){ return }\n\t\t\thorizontal = Math.abs(dx) > Math.abs(dy)\n\t\t\taxisLocked = true\n\t\t\tif (horizontal && trackEl){ trackEl.dataset.dragging = 'true' }\n\t\t}\n\t\tif (!horizontal){ return }\n\t\tmoved = true\n\t\tlet next = baseOffset + dx\n\t\tif (next > 0){ next = 0 }\n\t\tif (next < -ACTIONS_WIDTH){ next = -ACTIONS_WIDTH - (next + ACTIONS_WIDTH) * 0.2 }\n\t\tcurrentOffset = next\n\t\tapplyTransform(next)\n\t}\n\n\tconst onPointerUp = (e)=> {\n\t\tif (!dragging){ return }\n\t\tdragging = false\n\t\te.currentTarget.releasePointerCapture?.(e.pointerId)\n\t\tif (!horizontal){ return }\n\t\tconst shouldOpen = currentOffset < -SWIPE_THRESHOLD\n\t\tsnap(shouldOpen)\n\t}\n\n\tconst handleContentClick = (e)=> {\n\t\tif (moved){ e.stopPropagation(); return }\n\t\tif (isOpen()){ e.stopPropagation(); snap(false); return }\n\t}\n\n\tconst handleActionClick = action=> (e)=> {\n\t\te.stopPropagation()\n\t\tsnap(false)\n\t\taction.onClick?.(e)\n\t}\n\n\treturn (\n\t\t<div className={wrapperClass}>\n\t\t\t<div\n\t\t\t\tclassName={trackClass}\n\t\t\t\tdata-dragging='false'\n\t\t\t\tonPointerCancel={onPointerUp}\n\t\t\t\tonPointerDown={onPointerDown}\n\t\t\t\tonPointerMove={onPointerMove}\n\t\t\t\tonPointerUp={onPointerUp}\n\t\t\t\tref={setTrackRef}\n\t\t\t>\n\t\t\t\t<div className={contentClass} onClick={handleContentClick}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t\t<div className={actionsClass}>\n\t\t\t\t\t{actions.map(action=> (\n\t\t\t\t\t\t// prevent tab focus when actions are hidden off-screen\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={actionBtnClass}\n\t\t\t\t\t\t\tonClick={handleActionClick(action)}\n\t\t\t\t\t\t\tstyle={{ background: action.color }}\n\t\t\t\t\t\t\ttabIndex={isOpen() ? 0 : -1}\n\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{action.label}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default SwipeReveal\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SwipeReveal.js","names":["css","createEffect","createSignal","_tmpl2","_template","_tmpl","ACTIONS_WIDTH","SWIPE_THRESHOLD","VELOCITY_THRESHOLD","RUBBER_BAND_LIMIT","wrapperClass","trackClass","actionsClass","actionBtnClass","contentClass","SwipeReveal","actions","children","onSwipeStart","onOpenChange","activeId","thisId","isOpen","trackEl","startX","startY","lastX","lastTime","baseOffset","currentOffset","velocity","dragging","axisLocked","horizontal","moved","setTrackRef","el","applyTransform","px","style","transform","setDragging","v","dataset","String","snap","toOpen","currentActive","undefined","onPointerDown","e","pointerType","button","clientX","clientY","performance","now","currentTarget","setPointerCapture","pointerId","onPointerMove","dx","dy","Math","abs","dt","next","exp","over","onPointerUp","releasePointerCapture","shouldOpen","handleContentClick","stopPropagation","handleActionClick","action","onClick","_el0","cloneNode","_el1","firstChild","_el2","_el3","nextSibling","_insert","_setProp","map","idx","label","background","color"],"sources":["../../src/components/SwipeReveal.jsx"],"sourcesContent":["import { css } from '@emotion/css'\nimport { createEffect, createSignal } from '@plastic-js/plastic'\n\nconst ACTIONS_WIDTH = 160\nconst SWIPE_THRESHOLD = ACTIONS_WIDTH / 2\nconst VELOCITY_THRESHOLD = 0.5 // px/ms — flick assist threshold\nconst RUBBER_BAND_LIMIT = 50 // max rubber-band offset in px\n\nconst wrapperClass = css`\n\tposition: relative;\n\toverflow: hidden;\n\tborder-radius: 12px;\n\tuser-select: none;\n`\n\nconst trackClass = css`\n\tdisplay: flex;\n\talign-items: stretch;\n\twill-change: transform;\n\ttransition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);\n\ttouch-action: pan-y;\n\t&[data-dragging=\"true\"] { transition: none; }\n`\n\nconst actionsClass = css`\n\tflex: 0 0 ${ACTIONS_WIDTH}px;\n\tdisplay: flex;\n`\n\nconst actionBtnClass = css`\n\tflex: 1;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder: none;\n\tcolor: #fff;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcursor: pointer;\n\tfont-family: inherit;\n`\n\nconst contentClass = css`\n\tflex: 0 0 100%;\n`\n\nconst SwipeReveal = ({\n\tactions, children, onSwipeStart, onOpenChange, activeId, thisId,\n})=> {\n\tconst isOpen = createSignal(false)\n\n\tlet trackEl = null\n\tlet startX = 0\n\tlet startY = 0\n\tlet lastX = 0\n\tlet lastTime = 0\n\tlet baseOffset = 0\n\tlet currentOffset = 0\n\tlet velocity = 0\n\tlet dragging = false\n\tlet axisLocked = false\n\tlet horizontal = false\n\tlet moved = false\n\n\tconst setTrackRef = (el)=> { trackEl = el }\n\n\tconst applyTransform = (px)=> {\n\t\tif (trackEl){ trackEl.style.transform = `translateX(${px}px)` }\n\t}\n\n\tconst setDragging = (v)=> {\n\t\tif (trackEl){ trackEl.dataset.dragging = String(v) }\n\t}\n\n\tconst snap = (toOpen)=> {\n\t\tcurrentOffset = toOpen ? -ACTIONS_WIDTH : 0\n\t\tsetDragging(false)\n\t\tapplyTransform(currentOffset)\n\t\tonOpenChange?.(toOpen)\n\t\tisOpen(toOpen)\n\t}\n\n\t// only one card open at a time — close when another card's swipe activates\n\tcreateEffect(()=> {\n\t\tconst currentActive = typeof activeId === 'function' ? activeId() : activeId\n\t\tif (currentActive !== undefined && currentActive !== thisId && isOpen()){\n\t\t\tisOpen(false)\n\t\t\tcurrentOffset = 0\n\t\t\tsetDragging(false)\n\t\t\tapplyTransform(0)\n\t\t}\n\t})\n\n\tconst onPointerDown = (e)=> {\n\t\tif (e.pointerType === 'mouse' && e.button !== 0){ return }\n\t\tstartX = e.clientX\n\t\tstartY = e.clientY\n\t\tlastX = e.clientX\n\t\tlastTime = performance.now()\n\t\tbaseOffset = isOpen() ? -ACTIONS_WIDTH : 0\n\t\tvelocity = 0\n\t\tdragging = true\n\t\taxisLocked = false\n\t\thorizontal = false\n\t\tmoved = false\n\t\tonSwipeStart?.()\n\t\te.currentTarget.setPointerCapture?.(e.pointerId)\n\t}\n\n\tconst onPointerMove = (e)=> {\n\t\tif (!dragging){ return }\n\t\tconst dx = e.clientX - startX\n\t\tconst dy = e.clientY - startY\n\t\tif (!axisLocked){\n\t\t\tif (Math.abs(dx) < 6 && Math.abs(dy) < 6){ return }\n\t\t\thorizontal = Math.abs(dx) > Math.abs(dy)\n\t\t\taxisLocked = true\n\t\t\tif (horizontal){ setDragging(true) }\n\t\t}\n\t\tif (!horizontal){ return }\n\t\tmoved = true\n\n\t\t// track velocity (px/ms)\n\t\tconst now = performance.now()\n\t\tconst dt = now - lastTime\n\t\tif (dt > 0){\n\t\t\tvelocity = (e.clientX - lastX) / dt\n\t\t}\n\t\tlastX = e.clientX\n\t\tlastTime = now\n\n\t\tlet next = baseOffset + dx\n\t\tif (next > 0){\n\t\t\t// rubber-band overscroll on the right\n\t\t\tnext = RUBBER_BAND_LIMIT * (1 - Math.exp(-next / RUBBER_BAND_LIMIT))\n\t\t}\n\t\tif (next < -ACTIONS_WIDTH){\n\t\t\t// rubber-band overscroll on the left\n\t\t\tconst over = -(next + ACTIONS_WIDTH)\n\t\t\tnext = -ACTIONS_WIDTH - RUBBER_BAND_LIMIT * (1 - Math.exp(-over / RUBBER_BAND_LIMIT))\n\t\t}\n\t\tcurrentOffset = next\n\t\tapplyTransform(next)\n\t}\n\n\tconst onPointerUp = (e)=> {\n\t\tif (!dragging){ return }\n\t\tdragging = false\n\t\te.currentTarget.releasePointerCapture?.(e.pointerId)\n\t\tif (!horizontal){ return }\n\n\t\t// flick assist: fast swipe toward open/close direction\n\t\tif (Math.abs(velocity) > VELOCITY_THRESHOLD){\n\t\t\tsnap(velocity < 0)\n\t\t\treturn\n\t\t}\n\t\tconst shouldOpen = currentOffset < -SWIPE_THRESHOLD\n\t\tsnap(shouldOpen)\n\t}\n\n\tconst handleContentClick = (e)=> {\n\t\tif (moved){ e.stopPropagation(); return }\n\t\tif (isOpen()){ e.stopPropagation(); snap(false); return }\n\t}\n\n\tconst handleActionClick = action=> (e)=> {\n\t\te.stopPropagation()\n\t\tsnap(false)\n\t\taction.onClick?.(e)\n\t}\n\n\treturn (\n\t\t<div className={wrapperClass}>\n\t\t\t<div\n\t\t\t\tclassName={trackClass}\n\t\t\t\tdata-dragging='false'\n\t\t\t\tonPointerCancel={onPointerUp}\n\t\t\t\tonPointerDown={onPointerDown}\n\t\t\t\tonPointerMove={onPointerMove}\n\t\t\t\tonPointerUp={onPointerUp}\n\t\t\t\tref={setTrackRef}\n\t\t\t>\n\t\t\t\t<div className={contentClass} onClick={handleContentClick}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t\t<div className={actionsClass}>\n\t\t\t\t\t{actions.map((action, idx)=> (\n\t\t\t\t\t\t// prevent tab focus when actions are hidden off-screen\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={actionBtnClass}\n\t\t\t\t\t\t\tkey={action.label ?? idx}\n\t\t\t\t\t\t\tonClick={handleActionClick(action)}\n\t\t\t\t\t\t\tstyle={{ background: action.color }}\n\t\t\t\t\t\t\ttabIndex={isOpen() ? 0 : -1}\n\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{action.label}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default SwipeReveal\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,9 @@ import Drawer_default from "./components/Drawer.js";
|
|
|
17
17
|
import Field_default from "./components/Field.js";
|
|
18
18
|
import Fieldset_default from "./components/Fieldset.js";
|
|
19
19
|
import FileUpload_default from "./components/FileUpload.js";
|
|
20
|
-
import
|
|
20
|
+
import FilterableSelect from "./components/FilterableSelect.js";
|
|
21
|
+
import SelectMobile from "./components/SelectMobile/index.js";
|
|
22
|
+
import FilterableSelectMobile from "./components/FilterableSelectMobile.js";
|
|
21
23
|
import FocusTrap_default from "./components/FocusTrap.js";
|
|
22
24
|
import HoverCard_default from "./components/HoverCard.js";
|
|
23
25
|
import Listbox_default from "./components/Listbox.js";
|
|
@@ -47,4 +49,4 @@ import ToggleGroup_default from "./components/ToggleGroup.js";
|
|
|
47
49
|
import Tooltip_default from "./components/Tooltip.js";
|
|
48
50
|
import Tour_default from "./components/Tour.js";
|
|
49
51
|
import TreeView_default from "./components/TreeView.js";
|
|
50
|
-
export { Accordion_default as Accordion, Avatar_default as Avatar, CardNumberInput, Carousel_default as Carousel, Checkbox_default as Checkbox, Clipboard_default as Clipboard, Collapsible_default as Collapsible, ColorPicker_default as ColorPicker, Combobox_default as Combobox, ConfirmDialog, DateInput_default as DateInput, DatePicker_default as DatePicker, Dialog_default as Dialog, Drawer_default as Drawer, Field_default as Field, Fieldset_default as Fieldset, FileUpload_default as FileUpload,
|
|
52
|
+
export { Accordion_default as Accordion, Avatar_default as Avatar, CardNumberInput, Carousel_default as Carousel, Checkbox_default as Checkbox, Clipboard_default as Clipboard, Collapsible_default as Collapsible, ColorPicker_default as ColorPicker, Combobox_default as Combobox, ConfirmDialog, DateInput_default as DateInput, DatePicker_default as DatePicker, Dialog_default as Dialog, Drawer_default as Drawer, Field_default as Field, Fieldset_default as Fieldset, FileUpload_default as FileUpload, FilterableSelect, FilterableSelectMobile, FocusTrap_default as FocusTrap, HoverCard_default as HoverCard, Icon, Listbox_default as Listbox, Menu_default as Menu, MoneyInput, NumberInput_default as NumberInput, Pagination_default as Pagination, Popover_default as Popover, Portal_default as Portal, Presence_default as Presence, Progress_default as Progress, RadioGroup_default as RadioGroup, RatingGroup_default as RatingGroup, SearchInput, Select_default as Select, SelectMobile, SignaturePad_default as SignaturePad, Skeleton, Slider_default as Slider, Splitter_default as Splitter, Steps_default as Steps, SwipeReveal, Switch_default as Switch, Tabs_default as Tabs, TagsInput_default as TagsInput, Toast_default as Toast, Toggle_default as Toggle, ToggleGroup_default as ToggleGroup, Tooltip_default as Tooltip, Tour_default as Tour, TreeView_default as TreeView };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FilteredSelect.js","names":["Combobox","useComboboxContext","Loop","createComputed","createContext","createEffect","createSignal","splitProps","useContext","collection","createCollection","_tmpl3","_template","_tmpl2","_tmpl","access","value","FilterContext","defaultItemToValue","item","String","id","defaultItemToString","label","name","defaultFilter","query","itemToString","toLowerCase","includes","Root","props","local","rest","itemToValue","filter","rawValue","normalizedValue","handleValueChange","v","length","onValueChange","inputValue","filteredItems","q","trim","list","items","_jsx","Provider","_mergeProps","children","onInputValueChange","HighlightSingleMatch","combobox","api","open","highlightedValue","setHighlightValue","Input","onInput","event","getInputProps","onChange","Items","each","ChevronIcon","cloneNode","CheckIcon","FilteredSelect","cn","classNames","openOnClick","Label","className","Control","control","input","disabled","placeholder","Trigger","trigger","type","Content","content","Empty","empty","emptyText","Item","key","ItemText","itemText","_el0","_insert","_setProp","check"],"sources":["../../src/components/FilteredSelect.jsx"],"sourcesContent":["import { Combobox, useComboboxContext } from '@plastic-js/ark'\nimport {\n\tLoop, createComputed, createContext, createEffect, createSignal, splitProps, useContext,\n} from '@plastic-js/plastic'\nimport { collection as createCollection } from '@zag-js/combobox'\n\n// FilteredSelect — a filterable single-select built on the pure `Combobox`\n// primitive's underlying headless machine.\n//\n// This file owns two things:\n// 1. The filter plumbing. The headless component is \"controlled collection\"\n// only: it expects you to build a @zag-js collection yourself and narrow it\n// down as the user types. The internal <Root> keeps a query in sync with the\n// input, filters the list and feeds a reactive collection back to the\n// headless Root.\n// 2. The public API / contract. The default-exported <FilteredSelect> assembles\n// the full structure (label, control, input, trigger, content, items) and\n// exposes a flat prop API. It is intentionally style-agnostic: every visual\n// slot is themed through the `classNames` map, so the styling layer\n// only has to supply CSS.\n\nconst access = (value)=> {\n\treturn typeof value === 'function' ? value() : value\n}\n\n// Exposes the filtered items to <Items> without forcing the caller to thread\n// them through props.\nconst FilterContext = createContext(null)\n\nconst defaultItemToValue = item=> String(item?.value ?? item?.id ?? item)\nconst defaultItemToString = item=> String(item?.label ?? item?.name ?? item)\n\n// Substring match on the item's display string. Override via the `filter` prop\n// for fuzzy/score-based matching.\nconst defaultFilter = (item, query, itemToString)=> itemToString(item).toLowerCase().includes(query)\n\nconst Root = (props = {})=> {\n\tconst [local, rest] = splitProps(props, [\n\t\t'items', 'itemToValue', 'itemToString', 'filter', 'inputValue', 'onInputValueChange', 'children',\n\t])\n\n\tconst itemToValue = local.itemToValue ?? defaultItemToValue\n\tconst itemToString = local.itemToString ?? defaultItemToString\n\tconst filter = local.filter ?? defaultFilter\n\n\t// zag-js/combobox expects value to be an array (it calls .join(\",\")).\n\t// Single-select means a 0‑ or 1‑element array.\n\tconst rawValue = access(rest.value)\n\tconst normalizedValue = rawValue == null ? [] : [rawValue]\n\n\tconst handleValueChange = (value)=> {\n\t\tconst v = value.length >= 1 ? value[0] : null\n\t\trest.onValueChange?.(v)\n\t}\n\n\t// Internal query mirrors the input. We seed it from any `inputValue` the\n\t// caller passed so an initial/controlled value still filters correctly.\n\tconst query = createSignal(access(local.inputValue) ?? '')\n\n\t// Recomputes on typing and whenever the source `items` change. An empty\n\t// query shows everything; otherwise only the matches survive.\n\tconst filteredItems = createComputed(()=> {\n\t\tconst q = query().trim().toLowerCase()\n\t\tconst list = access(local.items) ?? []\n\t\treturn q ? list.filter(item=> filter(item, q, itemToString)) : list\n\t})\n\n\tconst collection = createComputed(()=> createCollection({\n\t\titems: filteredItems(),\n\t\titemToValue,\n\t\titemToString,\n\t}))\n\n\treturn (\n\t\t<FilterContext.Provider value={filteredItems}>\n\t\t\t<Combobox.Root\n\t\t\t\t{...rest}\n\t\t\t\tvalue={normalizedValue}\n\t\t\t\tonValueChange={handleValueChange}\n\t\t\t\tcollection={collection}\n\t\t\t\tinputValue={query}\n\t\t\t\tonInputValueChange={(value)=> {\n\t\t\t\t\tquery(value)\n\t\t\t\t\tlocal.onInputValueChange?.(value)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<HighlightSingleMatch itemToValue={itemToValue} items={filteredItems} />\n\t\t\t\t{local.children}\n\t\t\t</Combobox.Root>\n\t\t</FilterContext.Provider>\n\t)\n}\n\n// When the open list narrows to a single match, pre-highlight it so the user\n// can confirm with Enter/Tab without arrowing down. We nudge the machine's own\n// (uncontrolled) highlight imperatively rather than controlling `highlightedValue`\n// — controlling it would pin the highlight in place and fight the machine, e.g.\n// blocking the highlight-clear on the next keystroke and the select-and-close on\n// Enter. We only set it; the machine still clears/moves it on input and navigation.\nconst HighlightSingleMatch = (props = {})=> {\n\tconst combobox = useComboboxContext()\n\tcreateEffect(()=> {\n\t\tconst api = combobox()\n\t\tconst list = props.items()\n\t\tif (!api.open || list.length !== 1){ return }\n\t\tconst value = props.itemToValue(list[0])\n\t\tif (api.highlightedValue !== value){ api.setHighlightValue(value) }\n\t})\n\treturn null\n}\n\n// The headless input only fires zag's INPUT.CHANGE on the native `change`\n// event — which, unlike React's onChange, fires on blur, not per keystroke. So\n// typing never reached the machine and the list never filtered. We bridge the\n// per-keystroke `input` event to zag's own onChange handler so every character\n// drives the same INPUT.CHANGE (open + filter) behaviour.\nconst Input = (props = {})=> {\n\tconst combobox = useComboboxContext()\n\treturn (\n\t\t<Combobox.Input\n\t\t\t{...props}\n\t\t\tonInput={event=> combobox().getInputProps().onChange?.(event)}\n\t\t/>\n\t)\n}\n\n// Render-prop over the filtered items. Place inside <Combobox.Content> and\n// return a <Combobox.Item> for each match. Backed by <Loop> so the list stays\n// reactive — a bare thunk returned from a component is unwrapped once and would\n// never re-filter.\nconst Items = (props = {})=> {\n\tconst items = useContext(FilterContext)\n\treturn (\n\t\t<Loop each={items ?? []}>\n\t\t\t{item=> props.children?.(item)}\n\t\t</Loop>\n\t)\n}\n\nfunction ChevronIcon(){\n\treturn (\n\t\t<svg\n\t\t\tfill='none' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round'\n\t\t\tstrokeWidth='2' viewBox='0 0 24 24'\n\t\t>\n\t\t\t<polyline points='6 9 12 15 18 9' />\n\t\t</svg>\n\t)\n}\n\nfunction CheckIcon(){\n\treturn (\n\t\t<svg\n\t\t\tfill='none' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round'\n\t\t\tstrokeWidth='2.5' viewBox='0 0 24 24'\n\t\t>\n\t\t\t<polyline points='20 6 9 17 4 12' />\n\t\t</svg>\n\t)\n}\n\n// Public component. `classNames` themes each slot (control/input/trigger/content/\n// empty/item/itemText/check/label); anything not split here (e.g. `filter`,\n// `name`) flows through to the filter Root.\nconst FilteredSelect = (props = {})=> {\n\tconst [local, rest] = splitProps(props, [\n\t\t'items',\n\t\t'itemToString',\n\t\t'itemToValue',\n\t\t'placeholder',\n\t\t'label',\n\t\t'openOnClick',\n\t\t'disabled',\n\t\t'classNames',\n\t\t'emptyText',\n\t])\n\n\tconst cn = local.classNames ?? {}\n\n\t// `value`, `onValueChange`, `filter`, `name`, … are not transformed here, so\n\t// they ride `rest` straight through to the underlying machine.\n\treturn (\n\t\t<Root\n\t\t\titemToString={local.itemToString}\n\t\t\titemToValue={local.itemToValue}\n\t\t\titems={local.items}\n\t\t\topenOnClick={local.openOnClick ?? true}\n\t\t\t{...rest}\n\t\t>\n\t\t\t{local.label && (\n\t\t\t\t<Combobox.Label className={cn.label}>\n\t\t\t\t\t{local.label}\n\t\t\t\t</Combobox.Label>\n\t\t\t)}\n\t\t\t<Combobox.Control className={cn.control}>\n\t\t\t\t<Input\n\t\t\t\t\tclassName={cn.input}\n\t\t\t\t\tdisabled={local.disabled}\n\t\t\t\t\tplaceholder={local.placeholder ?? 'Type to search...'}\n\t\t\t\t/>\n\t\t\t\t<Combobox.Trigger className={cn.trigger} type='button'>\n\t\t\t\t\t<ChevronIcon />\n\t\t\t\t</Combobox.Trigger>\n\t\t\t</Combobox.Control>\n\t\t\t<Combobox.Content className={cn.content}>\n\t\t\t\t<Combobox.Empty className={cn.empty}>\n\t\t\t\t\t{local.emptyText ?? 'No results found'}\n\t\t\t\t</Combobox.Empty>\n\t\t\t\t<Items>\n\t\t\t\t\t{item=> (\n\t\t\t\t\t\t<Combobox.Item className={cn.item} item={item} key={local.itemToValue?.(item) ?? item}>\n\t\t\t\t\t\t\t<Combobox.ItemText className={cn.itemText}>\n\t\t\t\t\t\t\t\t{local.itemToString?.(item) ?? String(item)}\n\t\t\t\t\t\t\t</Combobox.ItemText>\n\t\t\t\t\t\t\t<span className={cn.check}>\n\t\t\t\t\t\t\t\t<CheckIcon />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</Combobox.Item>\n\t\t\t\t\t)}\n\t\t\t\t</Items>\n\t\t\t</Combobox.Content>\n\t\t</Root>\n\t)\n}\n\nexport default FilteredSelect\n"],"mappings":"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"}
|