@octanejs/select 0.1.1
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/LICENSE +21 -0
- package/README.md +43 -0
- package/UPSTREAM.md +66 -0
- package/package.json +89 -0
- package/src/accessibility-types.ts +70 -0
- package/src/animated/component-element.ts +5 -0
- package/src/animated/index.ts +32 -0
- package/src/animated/input.tsrx +24 -0
- package/src/animated/multi-value.tsrx +23 -0
- package/src/animated/placeholder.tsrx +19 -0
- package/src/animated/single-value.tsrx +18 -0
- package/src/animated/transitions.tsrx +90 -0
- package/src/animated/value-container.tsrx +96 -0
- package/src/animated-registry.ts +10 -0
- package/src/async-creatable.tsrx +30 -0
- package/src/async.tsrx +19 -0
- package/src/base.ts +7 -0
- package/src/builtins.ts +13 -0
- package/src/components.tsrx +875 -0
- package/src/creatable.tsrx +26 -0
- package/src/default-styles.ts +445 -0
- package/src/diacritics.ts +245 -0
- package/src/filters.ts +49 -0
- package/src/index.ts +66 -0
- package/src/menu-placement.ts +143 -0
- package/src/scroll-manager.ts +112 -0
- package/src/select.tsrx +1621 -0
- package/src/state-managed-select.tsrx +19 -0
- package/src/style-adapter.d.ts +25 -0
- package/src/style-adapter.mjs +53 -0
- package/src/style-context.tsrx +37 -0
- package/src/styles.ts +53 -0
- package/src/theme.ts +35 -0
- package/src/types.ts +147 -0
- package/src/use-async.tsrx +146 -0
- package/src/use-creatable.tsrx +151 -0
- package/src/use-state-manager.tsrx +90 -0
- package/src/utils.ts +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Jed Watson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @octanejs/select
|
|
2
|
+
|
|
3
|
+
An Octane binding for [`react-select@5.10.2`](https://github.com/JedWatson/react-select/tree/052e864b4990a67c4ee416851c34d1eb7b58267b).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @octanejs/select
|
|
9
|
+
pnpm add @octanejs/select
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The package preserves React Select's root, `base`, `async`, `animated`, `creatable`, and `async-creatable` entry points. Its default styles, component replacement contract, state management, asynchronous loading, creatable options, transitions, accessibility markup, keyboard/pointer/touch behavior, portals, and CSP nonce support are covered by executable React-oracle tests.
|
|
13
|
+
|
|
14
|
+
```tsrx
|
|
15
|
+
import Select from '@octanejs/select';
|
|
16
|
+
|
|
17
|
+
const options = [
|
|
18
|
+
{ value: 'chocolate', label: 'Chocolate' },
|
|
19
|
+
{ value: 'strawberry', label: 'Strawberry' },
|
|
20
|
+
{ value: 'vanilla', label: 'Vanilla' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export function FlavorPicker() {
|
|
24
|
+
return <Select options={options} />;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The public API follows the pinned React package. Import optional variants through the equivalent subpaths:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import AsyncSelect from '@octanejs/select/async';
|
|
32
|
+
import makeAnimated from '@octanejs/select/animated';
|
|
33
|
+
import CreatableSelect from '@octanejs/select/creatable';
|
|
34
|
+
import AsyncCreatableSelect from '@octanejs/select/async-creatable';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The animated entry point uses `@octanejs/transition-group`; consumers do not need to configure it separately.
|
|
38
|
+
|
|
39
|
+
See [UPSTREAM.md](./UPSTREAM.md) for the exact source pin, public-surface crosswalk, retained upstream evidence, and verification commands.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT. The pinned upstream notice is retained in [LICENSE](./LICENSE).
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Upstream provenance
|
|
2
|
+
|
|
3
|
+
This U1 audit targets the complete published `react-select` package, including
|
|
4
|
+
all six JavaScript API entry points. It is not a proposal for an unstyled or
|
|
5
|
+
headless subset.
|
|
6
|
+
|
|
7
|
+
| Field | Pinned value |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Package | `react-select@5.10.2` |
|
|
10
|
+
| Supported upstream range | exactly `5.10.2` |
|
|
11
|
+
| License | MIT |
|
|
12
|
+
| npm integrity | `sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==` |
|
|
13
|
+
| npm SHA-1 | `8dffc69dfd7d74684d9613e6eb27204e3b99e127` |
|
|
14
|
+
| Canonical tag object | `dd5ed998713af85f16c31c1f093d71d3c1e0e1bd` |
|
|
15
|
+
| Canonical commit | `052e864b4990a67c4ee416851c34d1eb7b58267b` |
|
|
16
|
+
| License SHA-256 | `d736dd18c7e53f88217fa2106c748f1a1687bb91d69a1f673fa685269402d784` |
|
|
17
|
+
| Published files | 138 |
|
|
18
|
+
| Published unpacked bytes | 725,500 |
|
|
19
|
+
| Pristine React oracle | `react@16.14.0` and `react-dom@16.14.0` via `react-select-pristine-react` / `react-select-pristine-react-dom` |
|
|
20
|
+
| Type oracle | `@types/react@19.2.17` and `@types/react-dom@19.2.3` (literal exact pins in `package.json`) for the required `tsc` lane against pinned `react-select@5.10.2` declarations |
|
|
21
|
+
| Pristine Jest | `jest@25.5.4`, `jest-environment-jsdom@25.5.0`, `babel-jest@25.5.1` |
|
|
22
|
+
| Pristine Testing Library | `@testing-library/react@12.1.4`, `@testing-library/jest-dom@6.9.1`, `@testing-library/user-event@10.4.1` |
|
|
23
|
+
| Pristine Emotion | `@emotion/react@11.9.3`, `@emotion/jest@11.5.0`, `@emotion/cache@11.9.3` |
|
|
24
|
+
|
|
25
|
+
The published API has root, `base`, `async`, `animated`, `creatable`, and
|
|
26
|
+
`async-creatable` JavaScript entry points plus `package.json`. The canonical
|
|
27
|
+
source boundary contains 61 files under `packages/select/src`, including
|
|
28
|
+
five runtime test files, five snapshot artifacts, and 79 Jest cases at the
|
|
29
|
+
pinned commit. The root package directly depends on `@emotion/react`,
|
|
30
|
+
`@emotion/cache`, and `react-transition-group`.
|
|
31
|
+
|
|
32
|
+
The MIT license permits copying, modifying, and distributing an Octane port as
|
|
33
|
+
long as the copyright and permission notice are retained. The canonical package
|
|
34
|
+
source, five Jest suites, five snapshots, package metadata, and license are
|
|
35
|
+
preserved under `upstream/`. `upstream/SHA256SUMS` locks all 63 files
|
|
36
|
+
byte-for-byte. Run `pnpm --filter @octanejs/select upstream:verify` to
|
|
37
|
+
reject a changed, missing, or additional upstream artifact. The six public
|
|
38
|
+
JavaScript entry points and all 20 runtime exports are tracked fail-closed in
|
|
39
|
+
`audit/export-crosswalk.json`.
|
|
40
|
+
|
|
41
|
+
## Public surface disposition
|
|
42
|
+
|
|
43
|
+
| Entry point | Runtime exports | Octane disposition |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `react-select` | `default`, `NonceProvider`, `components`, `createFilter`, `defaultTheme`, `mergeStyles`, `useStateManager` | Ported and tested |
|
|
46
|
+
| `react-select/base` | `default`, `defaultProps` | Ported and tested |
|
|
47
|
+
| `react-select/async` | `default`, `useAsync` | Ported and tested |
|
|
48
|
+
| `react-select/animated` | `default`, `Input`, `MultiValue`, `Placeholder`, `SingleValue`, `ValueContainer` | Ported and tested through `@octanejs/transition-group` |
|
|
49
|
+
| `react-select/creatable` | `default`, `useCreatable` | Ported and tested |
|
|
50
|
+
| `react-select/async-creatable` | `default` | Ported and tested |
|
|
51
|
+
|
|
52
|
+
The paired fixtures in `typetests/upstream.ts` and `typetests/local.ts` compile the same consumer-shaped public types against all six entry points of the pinned React package and the Octane port. `audit/type-parity.json` inventories their assertion groups, runs an executable structural comparison under the permitted import-root transforms, and the control tests reject skipped files, deleted assertions, and removed `@ts-expect-error` markers. A separate cross-environment fixture adds exact or bidirectional structural checks for shared pure value/action contracts and selected non-renderer members: core props, async and creatable configuration, imperative methods, plus component, style, class-name, and accessibility key sets. It is intentionally not described as an exhaustive value-signature proof for every exported declaration. Browser fixtures have a dedicated typecheck project so Vite cannot hide invalid type-only imports. Renderer-owned types deliberately substitute `OctaneNode`, native DOM events, and Octane style objects for ReactNode, synthetic events, and Emotion CSS objects; these consumer-visible adaptations are recorded in `status.json`, `audit/react-parity.json`, and fail-closed in `audit/export-crosswalk.json`. The crosswalk is verified against the actual pinned package exports so a new or removed upstream runtime export fails the repository gate.
|
|
53
|
+
|
|
54
|
+
## Upstream test disposition
|
|
55
|
+
|
|
56
|
+
The five canonical Jest suites and their snapshots are retained verbatim under `upstream/src/__tests__`. The required pristine Jest lane (`execution.kind: "jest-full"`) executes them unchanged against the vendored React source with the pinned React 16.14.0, Jest 25.5.x, Testing Library, and Emotion stack recorded above. It verifies 255 passing Jest identities and five snapshots; the three canonical upstream skips are inventoried as individually justified `not-applicable` dispositions in `audit/adaptation.json` (they are not counted as adapted evidence). The `select` project's narrowly owned `tests/upstream/**` lane runs all 255 pristine identities one-for-one across all five suites, including all five adapted snapshots, with `verification: verified` and zero pending adaptations. The other Octane tests remain ordinary repo-authored differential, SSR, browser, verifier, and crosswalk evidence:
|
|
57
|
+
|
|
58
|
+
| Retained suite | Executable Octane evidence |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `Select.test.tsx` | 186 adapted cases plus full Select SSR and Chromium behavior, styles/components, accessibility, forms, keyboard, mouse, touch, focus, placement, portals, and multi-value navigation |
|
|
61
|
+
| `StateManaged.test.tsx` | All 37 cases adapted plus controlled/uncontrolled precedence, transitions, and callbacks in SSR and Chromium |
|
|
62
|
+
| `Async.test.tsx` | All 9 cases adapted plus initial SSR state and broader async behavior in Chromium |
|
|
63
|
+
| `Creatable.test.tsx` | All 18 cases adapted plus creation metadata, delegated creation, option placement, and suppression differentials |
|
|
64
|
+
| `AsyncCreatable.test.tsx` | All 5 cases adapted plus composed async/creatable public contract and export coverage |
|
|
65
|
+
|
|
66
|
+
`audit/adapted-runtime.json` inventories only the adapted upstream lane. `audit/test-classifications.json` classifies every authored test under `tests/`, including the five adapted upstream suites, so a missing disposition cannot stay green. Run `pnpm --filter @octanejs/select test` to verify the vendored pin, fail-closed export crosswalk, pristine upstream suite, and Octane runtime lanes; run `pnpm --filter @octanejs/select typecheck` for paired type evidence.
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octanejs/select",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "React Select bindings for the Octane renderer.",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/octanejs/octane.git",
|
|
13
|
+
"directory": "packages/select"
|
|
14
|
+
},
|
|
15
|
+
"main": "src/index.ts",
|
|
16
|
+
"module": "src/index.ts",
|
|
17
|
+
"types": "src/index.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"README.md",
|
|
21
|
+
"UPSTREAM.md"
|
|
22
|
+
],
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./src/index.ts",
|
|
25
|
+
"./base": "./src/base.ts",
|
|
26
|
+
"./async": "./src/async.tsrx",
|
|
27
|
+
"./animated": "./src/animated/index.ts",
|
|
28
|
+
"./creatable": "./src/creatable.tsrx",
|
|
29
|
+
"./async-creatable": "./src/async-creatable.tsrx"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@emotion/cache": "11.9.3",
|
|
33
|
+
"@emotion/serialize": "1.1.0",
|
|
34
|
+
"@emotion/utils": "1.2.0",
|
|
35
|
+
"@floating-ui/dom": "1.8.0",
|
|
36
|
+
"memoize-one": "6.0.0",
|
|
37
|
+
"@octanejs/transition-group": "0.0.16"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"octane": "0.1.50"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/core": "7.19.6",
|
|
44
|
+
"@babel/plugin-proposal-class-properties": "7.18.6",
|
|
45
|
+
"@babel/plugin-proposal-private-methods": "7.18.6",
|
|
46
|
+
"@babel/plugin-transform-runtime": "7.14.3",
|
|
47
|
+
"@babel/preset-env": "7.19.4",
|
|
48
|
+
"@babel/preset-react": "7.18.6",
|
|
49
|
+
"@babel/preset-typescript": "7.18.6",
|
|
50
|
+
"@babel/runtime": "7.19.4",
|
|
51
|
+
"@emotion/babel-plugin": "11.10.2",
|
|
52
|
+
"@emotion/jest": "11.5.0",
|
|
53
|
+
"@emotion/react": "11.9.3",
|
|
54
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
55
|
+
"@testing-library/react": "12.1.4",
|
|
56
|
+
"@testing-library/user-event": "10.4.1",
|
|
57
|
+
"@types/react": "19.2.17",
|
|
58
|
+
"@types/react-dom": "19.2.3",
|
|
59
|
+
"@tsrx/react": "^0.2.61",
|
|
60
|
+
"babel-jest": "25.5.1",
|
|
61
|
+
"happy-dom": "^20.11.0",
|
|
62
|
+
"jest": "25.5.4",
|
|
63
|
+
"jest-environment-jsdom": "25.5.0",
|
|
64
|
+
"jest-in-case": "1.0.2",
|
|
65
|
+
"playwright": "^1.61.1",
|
|
66
|
+
"react": "^19.2.7",
|
|
67
|
+
"react-dom": "^19.2.7",
|
|
68
|
+
"react-select": "5.10.2",
|
|
69
|
+
"react-select-pristine-react": "npm:react@16.14.0",
|
|
70
|
+
"react-select-pristine-react-dom": "npm:react-dom@16.14.0",
|
|
71
|
+
"use-isomorphic-layout-effect": "1.2.1",
|
|
72
|
+
"vite": "^8.1.5",
|
|
73
|
+
"vitest": "^4.1.10",
|
|
74
|
+
"octane": "0.1.50"
|
|
75
|
+
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=22.22.2"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"test": "pnpm upstream:verify && pnpm crosswalk:verify && pnpm test:octane && pnpm test:pristine",
|
|
81
|
+
"test:octane": "vitest run --root ../.. --project select --project select-differential --project select-browser",
|
|
82
|
+
"test:pristine": "pnpm test:upstream",
|
|
83
|
+
"test:upstream": "jest --config tests/upstream-jest.config.cjs --rootDir upstream --runInBand --no-watchman",
|
|
84
|
+
"parity:inventory": "node ../../scripts/react-parity/react-select-runtime-inventory.mjs && node ../../scripts/react-parity/react-select-adaptation.mjs --write",
|
|
85
|
+
"typecheck": "tsrx-tsc --noEmit -p tsconfig.json && tsrx-tsc --noEmit -p tests/browser/tsconfig.json && tsrx-tsc --noEmit -p tests/upstream/tsconfig.json && tsrx-tsc --noEmit -p typetests/tsconfig.local.json && tsrx-tsc --noEmit -p typetests/tsconfig.compat.json && tsc --noEmit -p typetests/tsconfig.upstream.json",
|
|
86
|
+
"upstream:verify": "node scripts/verify-upstream.mjs",
|
|
87
|
+
"crosswalk:verify": "node scripts/verify-crosswalk.mjs"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionMeta,
|
|
3
|
+
GroupBase,
|
|
4
|
+
InitialInputFocusedActionMeta,
|
|
5
|
+
OnChangeValue,
|
|
6
|
+
Options,
|
|
7
|
+
OptionsOrGroups,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
export type OptionContext = 'menu' | 'value';
|
|
11
|
+
export type GuidanceContext = 'menu' | 'input' | 'value';
|
|
12
|
+
export type AriaSelection<Option, IsMulti extends boolean> =
|
|
13
|
+
| InitialInputFocusedActionMeta<Option, IsMulti>
|
|
14
|
+
| (ActionMeta<Option> & {
|
|
15
|
+
value: OnChangeValue<Option, IsMulti>;
|
|
16
|
+
option?: Option;
|
|
17
|
+
options?: Options<Option>;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export interface AriaGuidanceProps {
|
|
21
|
+
'aria-label': string | undefined;
|
|
22
|
+
context: GuidanceContext;
|
|
23
|
+
isSearchable: boolean;
|
|
24
|
+
isMulti: boolean;
|
|
25
|
+
isDisabled: boolean | null;
|
|
26
|
+
tabSelectsValue: boolean;
|
|
27
|
+
isInitialFocus: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AriaOnChangeProps<Option, IsMulti extends boolean> = AriaSelection<Option, IsMulti> & {
|
|
31
|
+
label: string;
|
|
32
|
+
labels: string[];
|
|
33
|
+
isDisabled: boolean | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface AriaOnFilterProps {
|
|
37
|
+
inputValue: string;
|
|
38
|
+
resultsMessage: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AriaOnFocusProps<Option, Group extends GroupBase<Option>> {
|
|
42
|
+
context: OptionContext;
|
|
43
|
+
focused: Option;
|
|
44
|
+
isDisabled: boolean;
|
|
45
|
+
isSelected: boolean;
|
|
46
|
+
label: string;
|
|
47
|
+
options: OptionsOrGroups<Option, Group>;
|
|
48
|
+
selectValue: Options<Option>;
|
|
49
|
+
isAppleDevice: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AriaGuidance = (props: AriaGuidanceProps) => string;
|
|
53
|
+
export type AriaOnChange<Option, IsMulti extends boolean> = (
|
|
54
|
+
props: AriaOnChangeProps<Option, IsMulti>,
|
|
55
|
+
) => string;
|
|
56
|
+
export type AriaOnFilter = (props: AriaOnFilterProps) => string;
|
|
57
|
+
export type AriaOnFocus<Option, Group extends GroupBase<Option> = GroupBase<Option>> = (
|
|
58
|
+
props: AriaOnFocusProps<Option, Group>,
|
|
59
|
+
) => string;
|
|
60
|
+
|
|
61
|
+
export interface AriaLiveMessages<
|
|
62
|
+
Option,
|
|
63
|
+
IsMulti extends boolean,
|
|
64
|
+
Group extends GroupBase<Option>,
|
|
65
|
+
> {
|
|
66
|
+
guidance?: (props: AriaGuidanceProps) => string;
|
|
67
|
+
onChange?: (props: AriaOnChangeProps<Option, IsMulti>) => string;
|
|
68
|
+
onFilter?: (props: AriaOnFilterProps) => string;
|
|
69
|
+
onFocus?: (props: AriaOnFocusProps<Option, Group>) => string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import memoize from 'memoize-one';
|
|
2
|
+
import { defaultComponents, type SelectComponentsGeneric } from '../components.tsrx';
|
|
3
|
+
import { animatedInput } from './input.tsrx';
|
|
4
|
+
import { animatedMultiValue } from './multi-value.tsrx';
|
|
5
|
+
import { animatedPlaceholder } from './placeholder.tsrx';
|
|
6
|
+
import { animatedSingleValue } from './single-value.tsrx';
|
|
7
|
+
import { animatedValueContainer } from './value-container.tsrx';
|
|
8
|
+
|
|
9
|
+
function makeAnimated(
|
|
10
|
+
externalComponents: Partial<SelectComponentsGeneric> = {},
|
|
11
|
+
): Partial<SelectComponentsGeneric> {
|
|
12
|
+
const components = defaultComponents({ components: externalComponents });
|
|
13
|
+
const { Input, MultiValue, Placeholder, SingleValue, ValueContainer, ...rest } = components;
|
|
14
|
+
return {
|
|
15
|
+
Input: animatedInput(Input),
|
|
16
|
+
MultiValue: animatedMultiValue(MultiValue),
|
|
17
|
+
Placeholder: animatedPlaceholder(Placeholder),
|
|
18
|
+
SingleValue: animatedSingleValue(SingleValue),
|
|
19
|
+
ValueContainer: animatedValueContainer(ValueContainer),
|
|
20
|
+
...rest,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const AnimatedComponents = makeAnimated();
|
|
25
|
+
|
|
26
|
+
export const Input = AnimatedComponents.Input;
|
|
27
|
+
export const MultiValue = AnimatedComponents.MultiValue;
|
|
28
|
+
export const Placeholder = AnimatedComponents.Placeholder;
|
|
29
|
+
export const SingleValue = AnimatedComponents.SingleValue;
|
|
30
|
+
export const ValueContainer = AnimatedComponents.ValueContainer;
|
|
31
|
+
|
|
32
|
+
export default memoize(makeAnimated);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TransitionProps } from '@octanejs/transition-group';
|
|
2
|
+
import type { InputProps } from '../components.tsrx';
|
|
3
|
+
import { asComponentElement } from './component-element';
|
|
4
|
+
|
|
5
|
+
type InputComponent = typeof import('../components.tsrx').Input;
|
|
6
|
+
type AnimatedTransitionProps =
|
|
7
|
+
Pick<TransitionProps, 'in' | 'onExited' | 'appear' | 'enter' | 'exit'>;
|
|
8
|
+
export type AnimatedInputProps = InputProps & AnimatedTransitionProps;
|
|
9
|
+
|
|
10
|
+
export function animatedInput(WrappedComponent: InputComponent): InputComponent {
|
|
11
|
+
function AnimatedInput(props: AnimatedInputProps) {
|
|
12
|
+
const {
|
|
13
|
+
in: _in,
|
|
14
|
+
onExited: _onExited,
|
|
15
|
+
appear: _appear,
|
|
16
|
+
enter: _enter,
|
|
17
|
+
exit: _exit,
|
|
18
|
+
...inputProps
|
|
19
|
+
} = props;
|
|
20
|
+
return asComponentElement<InputComponent>(createElement(WrappedComponent, inputProps));
|
|
21
|
+
}
|
|
22
|
+
return AnimatedInput;
|
|
23
|
+
}
|
|
24
|
+
import { createElement } from 'octane';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TransitionProps } from '@octanejs/transition-group';
|
|
2
|
+
import type { MultiValueProps } from '../components.tsrx';
|
|
3
|
+
import { Collapse } from './transitions.tsrx';
|
|
4
|
+
import { asComponentElement } from './component-element';
|
|
5
|
+
|
|
6
|
+
type MultiValueComponent = typeof import('../components.tsrx').MultiValue;
|
|
7
|
+
type AnimatedTransitionProps = Pick<TransitionProps, 'in' | 'onExited'>;
|
|
8
|
+
export type AnimatedMultiValueProps = MultiValueProps & AnimatedTransitionProps;
|
|
9
|
+
|
|
10
|
+
export function animatedMultiValue(WrappedComponent: MultiValueComponent): MultiValueComponent {
|
|
11
|
+
function AnimatedMultiValue(props: AnimatedMultiValueProps) {
|
|
12
|
+
const { in: inProp, onExited, ...multiValueProps } = props;
|
|
13
|
+
return asComponentElement<MultiValueComponent>(
|
|
14
|
+
createElement(
|
|
15
|
+
Collapse,
|
|
16
|
+
{ in: inProp, onExited },
|
|
17
|
+
createElement(WrappedComponent, { cropWithEllipsis: inProp, ...multiValueProps }),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return AnimatedMultiValue;
|
|
22
|
+
}
|
|
23
|
+
import { createElement } from 'octane';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PlaceholderProps } from '../components.tsrx';
|
|
2
|
+
import { collapseDuration, Fade } from './transitions.tsrx';
|
|
3
|
+
import { asComponentElement } from './component-element';
|
|
4
|
+
|
|
5
|
+
type PlaceholderComponent = typeof import('../components.tsrx').Placeholder;
|
|
6
|
+
|
|
7
|
+
export function animatedPlaceholder(WrappedComponent: PlaceholderComponent): PlaceholderComponent {
|
|
8
|
+
function AnimatedPlaceholder(props: PlaceholderProps) {
|
|
9
|
+
return asComponentElement<PlaceholderComponent>(
|
|
10
|
+
createElement(Fade, {
|
|
11
|
+
component: WrappedComponent as never,
|
|
12
|
+
duration: props.isMulti ? collapseDuration : 1,
|
|
13
|
+
...props,
|
|
14
|
+
}),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return AnimatedPlaceholder;
|
|
18
|
+
}
|
|
19
|
+
import { createElement } from 'octane';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SingleValueProps } from '../components.tsrx';
|
|
2
|
+
import { Fade } from './transitions.tsrx';
|
|
3
|
+
import { asComponentElement } from './component-element';
|
|
4
|
+
|
|
5
|
+
type SingleValueComponent = typeof import('../components.tsrx').SingleValue;
|
|
6
|
+
|
|
7
|
+
export function animatedSingleValue(WrappedComponent: SingleValueComponent): SingleValueComponent {
|
|
8
|
+
function AnimatedSingleValue(props: SingleValueProps) {
|
|
9
|
+
return asComponentElement<SingleValueComponent>(
|
|
10
|
+
createElement(Fade, {
|
|
11
|
+
component: WrappedComponent as never,
|
|
12
|
+
...props,
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return AnimatedSingleValue;
|
|
17
|
+
}
|
|
18
|
+
import { createElement } from 'octane';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type OctaneNode } from 'octane';
|
|
2
|
+
import { Transition, type TransitionStatus } from '@octanejs/transition-group';
|
|
3
|
+
|
|
4
|
+
type Component<Props> = (props: Props) => OctaneNode;
|
|
5
|
+
|
|
6
|
+
interface FadeProps<Props> {
|
|
7
|
+
component: Component<Props>;
|
|
8
|
+
duration?: number;
|
|
9
|
+
in?: boolean;
|
|
10
|
+
onExited?: (node?: Element) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Fade<Props extends Record<string, unknown>>(props: FadeProps<Props> & Props) @{
|
|
14
|
+
const {
|
|
15
|
+
component: Tag,
|
|
16
|
+
duration = 1,
|
|
17
|
+
in: inProp,
|
|
18
|
+
onExited: _onExited,
|
|
19
|
+
innerProps: rawConsumerInnerProps,
|
|
20
|
+
...componentProps
|
|
21
|
+
} = props;
|
|
22
|
+
const nodeRef = useRef<HTMLElement | null>(null);
|
|
23
|
+
const transition: Partial<Record<TransitionStatus, Record<string, string | number>>> = {
|
|
24
|
+
entering: { opacity: 0 },
|
|
25
|
+
entered: { opacity: 1, transition: `opacity ${duration}ms` },
|
|
26
|
+
exiting: { opacity: 0 },
|
|
27
|
+
exited: { opacity: 0 },
|
|
28
|
+
};
|
|
29
|
+
const consumerInnerProps = rawConsumerInnerProps as Record<string, unknown> | undefined;
|
|
30
|
+
const consumerStyle = (consumerInnerProps?.style ?? {}) as Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
<Transition mountOnEnter unmountOnExit in={inProp} timeout={duration} nodeRef={nodeRef}>
|
|
33
|
+
{(status: TransitionStatus) => <Tag
|
|
34
|
+
{...componentProps as unknown as Props}
|
|
35
|
+
innerProps={{
|
|
36
|
+
...consumerInnerProps,
|
|
37
|
+
style: { ...consumerStyle, ...transition[status] },
|
|
38
|
+
ref: nodeRef,
|
|
39
|
+
}}
|
|
40
|
+
/>}
|
|
41
|
+
</Transition>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const collapseDuration = 260;
|
|
45
|
+
|
|
46
|
+
interface CollapseProps {
|
|
47
|
+
children?: OctaneNode;
|
|
48
|
+
in?: boolean;
|
|
49
|
+
onExited?: (node?: Element) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Collapse(props: CollapseProps) @{
|
|
53
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
54
|
+
const [width, setWidth] = useState<number | 'auto'>('auto');
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const element = ref.current;
|
|
58
|
+
if (!element) return;
|
|
59
|
+
const frame = window.requestAnimationFrame(
|
|
60
|
+
() => setWidth(element.getBoundingClientRect().width),
|
|
61
|
+
);
|
|
62
|
+
return () => window.cancelAnimationFrame(frame);
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const styleFor = (status: TransitionStatus) => {
|
|
66
|
+
if (status === 'exiting') {
|
|
67
|
+
return { width: 0, transition: `width ${collapseDuration}ms ease-out` };
|
|
68
|
+
}
|
|
69
|
+
if (status === 'exited') return { width: 0 };
|
|
70
|
+
return { width };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
<Transition
|
|
74
|
+
enter={false}
|
|
75
|
+
mountOnEnter
|
|
76
|
+
unmountOnExit
|
|
77
|
+
in={props.in}
|
|
78
|
+
onExited={() => {
|
|
79
|
+
if (ref.current) props.onExited?.(ref.current);
|
|
80
|
+
}}
|
|
81
|
+
timeout={collapseDuration}
|
|
82
|
+
nodeRef={ref}
|
|
83
|
+
>
|
|
84
|
+
{(status: TransitionStatus) =>
|
|
85
|
+
<div
|
|
86
|
+
ref={ref}
|
|
87
|
+
style={{ overflow: 'hidden', whiteSpace: 'nowrap', ...styleFor(status) }}
|
|
88
|
+
>{props.children}</div>}
|
|
89
|
+
</Transition>
|
|
90
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
createElement,
|
|
5
|
+
Fragment,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useEffect,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'octane';
|
|
10
|
+
import { TransitionGroup } from '@octanejs/transition-group';
|
|
11
|
+
import type { ValueContainerProps } from '../components.tsrx';
|
|
12
|
+
import { markInspectableValueChildren } from '../animated-registry.ts';
|
|
13
|
+
import { asComponentElement } from './component-element';
|
|
14
|
+
|
|
15
|
+
type ValueContainerComponent = typeof import('../components.tsrx').ValueContainer;
|
|
16
|
+
|
|
17
|
+
function flattenKeyedChildren(children: unknown): unknown[] {
|
|
18
|
+
const flattened: unknown[] = [];
|
|
19
|
+
for (const child of Children.toArray(children)) {
|
|
20
|
+
if (isValidElement(child) && child.type === Fragment) {
|
|
21
|
+
for (const innerChild of Children.toArray(child.props.children)) {
|
|
22
|
+
flattened.push(
|
|
23
|
+
isValidElement(innerChild)
|
|
24
|
+
? cloneElement(innerChild, {
|
|
25
|
+
key: JSON.stringify([child.key, innerChild.key]),
|
|
26
|
+
})
|
|
27
|
+
: innerChild,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
flattened.push(child);
|
|
33
|
+
}
|
|
34
|
+
return flattened;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function animatedValueContainer(
|
|
38
|
+
WrappedComponent: ValueContainerComponent,
|
|
39
|
+
): ValueContainerComponent {
|
|
40
|
+
function AnimatedValueContainer(props: ValueContainerProps) {
|
|
41
|
+
return asComponentElement<ValueContainerComponent>(
|
|
42
|
+
createElement(props.isMulti ? MultiValueContainer : TransitionGroup, {
|
|
43
|
+
component: WrappedComponent,
|
|
44
|
+
...props,
|
|
45
|
+
children: flattenKeyedChildren(props.children),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return markInspectableValueChildren(AnimatedValueContainer);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface MultiValueContainerProps extends ValueContainerProps {
|
|
53
|
+
component: ValueContainerComponent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function MultiValueContainer(props: MultiValueContainerProps) @{
|
|
57
|
+
const { component, children, ...containerProps } = props;
|
|
58
|
+
const [displayFlex, setDisplayFlex] = useState(
|
|
59
|
+
props.isMulti && props.selectProps.controlShouldRenderValue && props.hasValue,
|
|
60
|
+
);
|
|
61
|
+
const [removingValue, setRemovingValue] = useState(false);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (props.hasValue && !displayFlex) setDisplayFlex(true);
|
|
65
|
+
}, [props.hasValue, displayFlex]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (removingValue && !props.hasValue && displayFlex) setDisplayFlex(false);
|
|
69
|
+
setRemovingValue(false);
|
|
70
|
+
}, [removingValue, props.hasValue, displayFlex]);
|
|
71
|
+
|
|
72
|
+
const selectComponents = props.selectProps.components as Record<string, unknown>;
|
|
73
|
+
const mappedChildren = Children.toArray(children).map((child) => {
|
|
74
|
+
if (!isValidElement(child) || child.type !== selectComponents.MultiValue) {
|
|
75
|
+
if (isValidElement(child) && child.type === selectComponents.Placeholder && displayFlex) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return child;
|
|
79
|
+
}
|
|
80
|
+
return cloneElement(child, { onExited: () => setRemovingValue(true) });
|
|
81
|
+
});
|
|
82
|
+
const innerProps = {
|
|
83
|
+
...props.innerProps,
|
|
84
|
+
style: {
|
|
85
|
+
...(props.innerProps?.style ?? {}) as Record<string, unknown>,
|
|
86
|
+
display: props.isMulti && props.hasValue || displayFlex ? 'flex' : 'grid',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
<TransitionGroup
|
|
91
|
+
component={component}
|
|
92
|
+
{...containerProps}
|
|
93
|
+
innerProps={innerProps}
|
|
94
|
+
children={mappedChildren}
|
|
95
|
+
/>
|
|
96
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const inspectableValueChildren = Symbol.for('octane.react-select.inspectable-value-children');
|
|
2
|
+
|
|
3
|
+
export function markInspectableValueChildren<T extends Function>(component: T): T {
|
|
4
|
+
Object.defineProperty(component, inspectableValueChildren, { value: true });
|
|
5
|
+
return component;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function needsInspectableValueChildren(component: Function): boolean {
|
|
9
|
+
return (component as unknown as Record<PropertyKey, unknown>)[inspectableValueChildren] === true;
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Select, type SelectProps } from './select.tsrx';
|
|
2
|
+
import { useAsync, type AsyncProps } from './use-async.tsrx';
|
|
3
|
+
import {
|
|
4
|
+
useCreatable,
|
|
5
|
+
type CreatableAdditionalProps,
|
|
6
|
+
type CreatableBaseProps,
|
|
7
|
+
} from './use-creatable.tsrx';
|
|
8
|
+
import { useStateManager } from './use-state-manager.tsrx';
|
|
9
|
+
import type { StateManagerProps } from './use-state-manager.tsrx';
|
|
10
|
+
import type { GroupBase } from './types';
|
|
11
|
+
|
|
12
|
+
export type AsyncCreatableProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> =
|
|
13
|
+
AsyncProps<Option, IsMulti, Group> & CreatableAdditionalProps<Option, Group> & StateManagerProps<
|
|
14
|
+
Option,
|
|
15
|
+
IsMulti,
|
|
16
|
+
Group
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
function AsyncCreatableSelect<
|
|
20
|
+
Option = unknown,
|
|
21
|
+
IsMulti extends boolean = false,
|
|
22
|
+
Group extends GroupBase<Option> = GroupBase<Option>,
|
|
23
|
+
>(props: AsyncCreatableProps<Option, IsMulti, Group>) @{
|
|
24
|
+
const asyncProps = useAsync<Option, IsMulti, Group>(props);
|
|
25
|
+
const stateProps = useStateManager<Option, IsMulti, Group>(asyncProps);
|
|
26
|
+
const creatableProps = useCreatable(stateProps as CreatableBaseProps<Option, IsMulti, Group>);
|
|
27
|
+
<Select {...creatableProps as SelectProps<Option, IsMulti, Group>} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default AsyncCreatableSelect;
|