Compare commits
47 Commits
9506d123f3
...
rw
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bac48d5dd | |||
| fbb65afc94 | |||
| 095ddf6162 | |||
|
|
0d9511df77 | ||
| b2817f4233 | |||
|
|
71403289c2 | ||
|
|
7025f316de | ||
|
|
32054118de | ||
|
|
017b6445fb | ||
|
|
7c1d47819a | ||
|
|
b514c906c8 | ||
|
|
6664c988b7 | ||
|
|
249c283819 | ||
|
|
1ce5c25098 | ||
|
|
30581de17e | ||
|
|
8784a28a30 | ||
|
|
1b2bf6282d | ||
|
|
abcf08f98e | ||
|
|
0ba8dca0b4 | ||
|
|
7cfefa9e6d | ||
|
|
e879abb43f | ||
|
|
9f04b36e7e | ||
|
|
03210a3a7a | ||
|
|
e6560aa990 | ||
|
|
5e922df97a | ||
|
|
abf9433c10 | ||
|
|
1284f46aa9 | ||
|
|
a1202f9b6d | ||
|
|
a8a172cbfe | ||
|
|
9f960a6729 | ||
|
|
864188c599 | ||
|
|
5fc02f9671 | ||
|
|
b4058f1ef3 | ||
|
|
0943ffc483 | ||
|
|
bd47e9d0ab | ||
|
|
57c72e656f | ||
|
|
b977308e54 | ||
|
|
54deac6ccc | ||
|
|
615b89360a | ||
|
|
64cfed8a67 | ||
|
|
d6b7fa4076 | ||
|
|
ad5bc14d7c | ||
|
|
5d8388c2db | ||
|
|
1f5999b2d1 | ||
|
|
cdcb5c2684 | ||
|
|
a50920d70e | ||
|
|
b49fadae83 |
@@ -1,13 +1,16 @@
|
|||||||
import { MantineProvider } from '@mantine/core';
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
|
|
||||||
export function PreviewDecorator(Story: any, { parameters }: any) {
|
export function PreviewDecorator(Story: any, { parameters }: any) {
|
||||||
console.log('Rendering decorator', parameters);
|
console.log('Rendering decorator', parameters);
|
||||||
return (
|
return (
|
||||||
<MantineProvider>
|
<MantineProvider>
|
||||||
<div style={{ height: 'calc(100vh - 64px)', width: 'calc(100vw - 64px)' }}>
|
<ModalsProvider>
|
||||||
<Story key={'mainStory'} />
|
<div style={{ height: 'calc(100vh - 64px)', width: 'calc(100vw - 64px)' }}>
|
||||||
</div>
|
<Story key={'mainStory'} />
|
||||||
|
</div>
|
||||||
|
</ModalsProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
102
CHANGELOG.md
102
CHANGELOG.md
@@ -1,5 +1,107 @@
|
|||||||
# @warkypublic/zustandsyncstore
|
# @warkypublic/zustandsyncstore
|
||||||
|
|
||||||
|
## 0.0.23
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3205411: Using the effect of array as feature.
|
||||||
|
|
||||||
|
## 0.0.22
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7c1d478: Possible selection fixes
|
||||||
|
|
||||||
|
## 0.0.21
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6664c98: Calls onchange on cell click since selection does not change.
|
||||||
|
|
||||||
|
## 0.0.20
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 30581de: Version bump
|
||||||
|
|
||||||
|
## 0.0.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1b2bf62: Fixed refresh bug
|
||||||
|
|
||||||
|
## 0.0.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7cfefa9: API props change bug
|
||||||
|
|
||||||
|
## 0.0.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 03210a3: Updated selected cols bug
|
||||||
|
|
||||||
|
## 0.0.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5e922df: A Few fixes
|
||||||
|
|
||||||
|
## 0.0.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a1202f9: Hopefully fix the options not always loading
|
||||||
|
|
||||||
|
## 0.0.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 864188c: Added searchfields
|
||||||
|
|
||||||
|
## 0.0.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b4058f1: Fixed search and allow row selection for only rows with keys
|
||||||
|
|
||||||
|
## 0.0.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 57c72e6: Search String and Better GoAPI functionality
|
||||||
|
|
||||||
|
## 0.0.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 54deac6: Added refs and exports, isEmpty
|
||||||
|
|
||||||
|
## 0.0.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 64cfed8: Scroll to and forwarded ref
|
||||||
|
|
||||||
|
## 0.0.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5d8388c: Added selectFirstRowOnMount and fixed selection of first row
|
||||||
|
|
||||||
|
## 0.0.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- cdcb5c2: Fixed memo of options in GridlerAPIAdaptor
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b49fada: Extra api options, local data options
|
||||||
|
|
||||||
## 0.0.6
|
## 0.0.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -16,19 +16,22 @@ const config = defineConfig([
|
|||||||
plugins: { js },
|
plugins: { js },
|
||||||
},
|
},
|
||||||
// reactHooks.configs['recommended-latest'],
|
// reactHooks.configs['recommended-latest'],
|
||||||
{...reactRefresh.configs.vite, ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],},
|
{ ...reactRefresh.configs.vite, ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] },
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
{
|
{
|
||||||
...pluginReact.configs.flat.recommended,
|
...pluginReact.configs.flat.recommended,
|
||||||
ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', '*stories.tsx'],
|
||||||
rules: {...pluginReact.configs.flat.recommended.rules,
|
rules: {
|
||||||
|
...pluginReact.configs.flat.recommended.rules,
|
||||||
'react/react-in-jsx-scope': 'off',
|
'react/react-in-jsx-scope': 'off',
|
||||||
}
|
'react-refresh/only-export-components': 'warn',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
perfectionist.configs['recommended-alphabetical'],
|
perfectionist.configs['recommended-alphabetical'],
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
21
package.json
21
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@warkypublic/oranguru",
|
"name": "@warkypublic/oranguru",
|
||||||
"author": "Warky Devs",
|
"author": "Warky Devs",
|
||||||
"version": "0.0.6",
|
"version": "0.0.23",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -49,12 +49,14 @@
|
|||||||
"./oranguru.css": "./src/oranguru.css"
|
"./oranguru.css": "./src/oranguru.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
||||||
"moment": "^2.30.1"
|
"moment": "^2.30.1"
|
||||||
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.29.7",
|
"@changesets/cli": "^2.29.7",
|
||||||
"@eslint/js": "^9.38.0",
|
"@eslint/js": "^9.38.0",
|
||||||
"@storybook/react-vite": "^9.1.13",
|
"@storybook/react-vite": "^9.1.15",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
@@ -62,14 +64,14 @@
|
|||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@typescript-eslint/parser": "^8.46.2",
|
"@typescript-eslint/parser": "^8.46.2",
|
||||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
"@vitejs/plugin-react-swc": "^4.2.0",
|
||||||
"eslint": "^9.38.0",
|
"eslint": "^9.38.0",
|
||||||
"eslint-config-mantine": "^4.0.3",
|
"eslint-config-mantine": "^4.0.3",
|
||||||
"eslint-plugin-perfectionist": "^4.15.1",
|
"eslint-plugin-perfectionist": "^4.15.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.0",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"eslint-plugin-storybook": "^9.1.13",
|
"eslint-plugin-storybook": "^9.1.15",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
@@ -81,23 +83,26 @@
|
|||||||
"prettier-eslint": "^16.4.2",
|
"prettier-eslint": "^16.4.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"storybook": "^9.1.13",
|
"storybook": "^9.1.15",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.2",
|
"typescript-eslint": "^8.46.2",
|
||||||
"vite": "^7.1.11",
|
"vite": "^7.1.12",
|
||||||
"vite-plugin-dts": "^4.5.4",
|
"vite-plugin-dts": "^4.5.4",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@glideapps/glide-data-grid": "^6.0.3",
|
"@glideapps/glide-data-grid": "^6.0.3",
|
||||||
"@mantine/core": "^8.3.1",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.3.1",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@mantine/notifications": "^8.3.5",
|
"@mantine/notifications": "^8.3.5",
|
||||||
|
"@mantine/modals": "^8.3.5",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@tanstack/react-query": "^5.90.5",
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"@warkypublic/artemis-kit": "^1.0.10",
|
"@warkypublic/artemis-kit": "^1.0.10",
|
||||||
"@warkypublic/zustandsyncstore": "^0.0.4",
|
"@warkypublic/zustandsyncstore": "^0.0.4",
|
||||||
|
"react-hook-form": "^7.71.0",
|
||||||
|
|
||||||
"immer": "^10.1.3",
|
"immer": "^10.1.3",
|
||||||
"react": ">= 19.0.0",
|
"react": ">= 19.0.0",
|
||||||
"react-dom": ">= 19.0.0",
|
"react-dom": ">= 19.0.0",
|
||||||
|
|||||||
377
pnpm-lock.yaml
generated
377
pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ importers:
|
|||||||
'@mantine/hooks':
|
'@mantine/hooks':
|
||||||
specifier: ^8.3.1
|
specifier: ^8.3.1
|
||||||
version: 8.3.1(react@19.2.0)
|
version: 8.3.1(react@19.2.0)
|
||||||
|
'@mantine/modals':
|
||||||
|
specifier: ^8.3.5
|
||||||
|
version: 8.3.12(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mantine/notifications':
|
'@mantine/notifications':
|
||||||
specifier: ^8.3.5
|
specifier: ^8.3.5
|
||||||
version: 8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
@@ -38,6 +41,9 @@ importers:
|
|||||||
moment:
|
moment:
|
||||||
specifier: ^2.30.1
|
specifier: ^2.30.1
|
||||||
version: 2.30.1
|
version: 2.30.1
|
||||||
|
react-hook-form:
|
||||||
|
specifier: ^7.71.0
|
||||||
|
version: 7.71.0(react@19.2.0)
|
||||||
use-sync-external-store:
|
use-sync-external-store:
|
||||||
specifier: '>= 1.4.0'
|
specifier: '>= 1.4.0'
|
||||||
version: 1.5.0(react@19.2.0)
|
version: 1.5.0(react@19.2.0)
|
||||||
@@ -52,8 +58,8 @@ importers:
|
|||||||
specifier: ^9.38.0
|
specifier: ^9.38.0
|
||||||
version: 9.38.0
|
version: 9.38.0
|
||||||
'@storybook/react-vite':
|
'@storybook/react-vite':
|
||||||
specifier: ^9.1.13
|
specifier: ^9.1.15
|
||||||
version: 9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.50.2)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.50.2)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
'@testing-library/jest-dom':
|
'@testing-library/jest-dom':
|
||||||
specifier: ^6.9.1
|
specifier: ^6.9.1
|
||||||
version: 6.9.1
|
version: 6.9.1
|
||||||
@@ -76,8 +82,8 @@ importers:
|
|||||||
specifier: ^8.46.2
|
specifier: ^8.46.2
|
||||||
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@vitejs/plugin-react-swc':
|
'@vitejs/plugin-react-swc':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.2.0
|
||||||
version: 4.1.0(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 4.2.0(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.38.0
|
specifier: ^9.38.0
|
||||||
version: 9.38.0(jiti@2.6.1)
|
version: 9.38.0(jiti@2.6.1)
|
||||||
@@ -91,14 +97,14 @@ importers:
|
|||||||
specifier: ^7.37.5
|
specifier: ^7.37.5
|
||||||
version: 7.37.5(eslint@9.38.0(jiti@2.6.1))
|
version: 7.37.5(eslint@9.38.0(jiti@2.6.1))
|
||||||
eslint-plugin-react-hooks:
|
eslint-plugin-react-hooks:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.1
|
||||||
version: 7.0.0(eslint@9.38.0(jiti@2.6.1))
|
version: 7.0.1(eslint@9.38.0(jiti@2.6.1))
|
||||||
eslint-plugin-react-refresh:
|
eslint-plugin-react-refresh:
|
||||||
specifier: ^0.4.24
|
specifier: ^0.4.24
|
||||||
version: 0.4.24(eslint@9.38.0(jiti@2.6.1))
|
version: 0.4.24(eslint@9.38.0(jiti@2.6.1))
|
||||||
eslint-plugin-storybook:
|
eslint-plugin-storybook:
|
||||||
specifier: ^9.1.13
|
specifier: ^9.1.15
|
||||||
version: 9.1.13(eslint@9.38.0(jiti@2.6.1))(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)
|
version: 9.1.15(eslint@9.38.0(jiti@2.6.1))(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)
|
||||||
global:
|
global:
|
||||||
specifier: ^4.4.0
|
specifier: ^4.4.0
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
@@ -133,8 +139,8 @@ importers:
|
|||||||
specifier: ^19.2.0
|
specifier: ^19.2.0
|
||||||
version: 19.2.0(react@19.2.0)
|
version: 19.2.0(react@19.2.0)
|
||||||
storybook:
|
storybook:
|
||||||
specifier: ^9.1.13
|
specifier: ^9.1.15
|
||||||
version: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.9.3
|
specifier: ~5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -142,17 +148,17 @@ importers:
|
|||||||
specifier: ^8.46.2
|
specifier: ^8.46.2
|
||||||
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.1.11
|
specifier: ^7.1.12
|
||||||
version: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
version: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
vite-plugin-dts:
|
vite-plugin-dts:
|
||||||
specifier: ^4.5.4
|
specifier: ^4.5.4
|
||||||
version: 4.5.4(@types/node@24.9.1)(rollup@4.50.2)(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 4.5.4(@types/node@24.9.1)(rollup@4.50.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
vite-tsconfig-paths:
|
vite-tsconfig-paths:
|
||||||
specifier: ^5.1.4
|
specifier: ^5.1.4
|
||||||
version: 5.1.4(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.2.4
|
specifier: ^4.0.3
|
||||||
version: 3.2.4(@types/node@24.9.1)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(sugarss@5.0.1(postcss@8.5.6))
|
version: 4.0.3(@types/node@24.9.1)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(sugarss@5.0.1(postcss@8.5.6))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -702,6 +708,14 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.x || ^19.x
|
react: ^18.x || ^19.x
|
||||||
|
|
||||||
|
'@mantine/modals@8.3.12':
|
||||||
|
resolution: {integrity: sha512-+uRyGe2lLy601qlMk+8aR9d/Aibu+dZi6Jcmvm5z8Gw4ocviyMMlnd8BLSQ/Jvib2OX8fWj+yUQN7FMQ4Rbwjw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@mantine/core': 8.3.12
|
||||||
|
'@mantine/hooks': 8.3.12
|
||||||
|
react: ^18.x || ^19.x
|
||||||
|
react-dom: ^18.x || ^19.x
|
||||||
|
|
||||||
'@mantine/notifications@8.3.5':
|
'@mantine/notifications@8.3.5':
|
||||||
resolution: {integrity: sha512-8TvzrPxfdtOLGTalv7Ei1hy2F6KbR3P7/V73yw3AOKhrf1ydS89sqV2ShbsucHGJk9Pto0wjdTPd8Q7pm5MAYw==}
|
resolution: {integrity: sha512-8TvzrPxfdtOLGTalv7Ei1hy2F6KbR3P7/V73yw3AOKhrf1ydS89sqV2ShbsucHGJk9Pto0wjdTPd8Q7pm5MAYw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -750,8 +764,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.35':
|
'@rolldown/pluginutils@1.0.0-beta.43':
|
||||||
resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==}
|
resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==}
|
||||||
|
|
||||||
'@rollup/pluginutils@5.3.0':
|
'@rollup/pluginutils@5.3.0':
|
||||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||||
@@ -892,43 +906,46 @@ packages:
|
|||||||
'@sinclair/typebox@0.27.8':
|
'@sinclair/typebox@0.27.8':
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
|
|
||||||
'@storybook/builder-vite@9.1.13':
|
'@standard-schema/spec@1.0.0':
|
||||||
resolution: {integrity: sha512-pmtIjU02ASJOZKdL8DoxWXJgZnpTDgD5WmMnjKJh9FaWmc2YiCW2Y6VRxPox96OM655jYHQe5+UIbk3Cwtwb4A==}
|
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||||
|
|
||||||
|
'@storybook/builder-vite@9.1.15':
|
||||||
|
resolution: {integrity: sha512-GZkx72cBnCuTL/cVOIWIicB4GCmZWx52zFGSC/qHOT/sKcUkrIoQSpVljqyPa66woHyUeSZX4mu7aGj5A27QVg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
|
|
||||||
'@storybook/csf-plugin@9.1.13':
|
'@storybook/csf-plugin@9.1.15':
|
||||||
resolution: {integrity: sha512-EMpzYuyt9FDcxxfBChWzfId50y8QMpdenviEQ8m+pa6c+ANx3pC5J6t7y0khD8TQu815sTy+nc6cc8PC45dPUA==}
|
resolution: {integrity: sha512-UThWh7V3+zd+71XdIsNFkrNslkjyaD/HQPnjWGWBCU4ZWNWRSPd3r0r02nH0zzo+NBi0V4vzNDg/PmfD51iaOg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
|
|
||||||
'@storybook/global@5.0.0':
|
'@storybook/global@5.0.0':
|
||||||
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
|
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
|
||||||
|
|
||||||
'@storybook/react-dom-shim@9.1.13':
|
'@storybook/react-dom-shim@9.1.15':
|
||||||
resolution: {integrity: sha512-/tMr9TmV3+98GEQO0S03k4gtKHGCpv9+k9Dmnv+TJK3TBz7QsaFEzMwe3gCgoTaebLACyVveDiZkWnCYAWB6NA==}
|
resolution: {integrity: sha512-l6smvNwxh6kp2U/BupzQ4/NSraTWysZcAe2x+GO5CiIIB8Jbi41XLu5XIHI/GQRnNqpXXE3uMImiHGOORmHEXA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
|
|
||||||
'@storybook/react-vite@9.1.13':
|
'@storybook/react-vite@9.1.15':
|
||||||
resolution: {integrity: sha512-mV1bZ1bpkNQygnuDo1xMGAS5ZXuoXFF0WGmr/BzNDGmRhZ1K1HQh42kC0w3PklckFBUwCFxmP58ZwTFzf+/dJA==}
|
resolution: {integrity: sha512-ORD3swzehAx6o7Sw/wmXr/L5Q4FANPsc1+G5s6OTfmbWNDy/e803h7lsUUFwokh7PeYgylJzbPMQ8HnkNghN0A==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
|
|
||||||
'@storybook/react@9.1.13':
|
'@storybook/react@9.1.15':
|
||||||
resolution: {integrity: sha512-B0UpYikKf29t8QGcdmumWojSQQ0phSDy/Ne2HYdrpNIxnUvHHUVOlGpq4lFcIDt52Ip5YG5GuAwJg3+eR4LCRg==}
|
resolution: {integrity: sha512-tdd1Od3roaEQ2rjqk1L15yR/N2y/SLNcpPNp3n9AQT1eDPdbKzIzue7u1eW9KUFdwSF9S8xG35roDUkKwBJogQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
typescript: '>= 4.9.x'
|
typescript: '>= 4.9.x'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
@@ -1234,8 +1251,8 @@ packages:
|
|||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
'@vitejs/plugin-react-swc@4.1.0':
|
'@vitejs/plugin-react-swc@4.2.0':
|
||||||
resolution: {integrity: sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==}
|
resolution: {integrity: sha512-/tesahXD1qpkGC6FzMoFOJj0RyZdw9xLELOL+6jbElwmWfwOnIVy+IfpY+o9JfD9PKaR/Eyb6DNrvbXpuvA+8Q==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4 || ^5 || ^6 || ^7
|
vite: ^4 || ^5 || ^6 || ^7
|
||||||
@@ -1243,6 +1260,9 @@ packages:
|
|||||||
'@vitest/expect@3.2.4':
|
'@vitest/expect@3.2.4':
|
||||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
|
'@vitest/expect@4.0.3':
|
||||||
|
resolution: {integrity: sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==}
|
||||||
|
|
||||||
'@vitest/mocker@3.2.4':
|
'@vitest/mocker@3.2.4':
|
||||||
resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
|
resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1254,21 +1274,41 @@ packages:
|
|||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/mocker@4.0.3':
|
||||||
|
resolution: {integrity: sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^6.0.0 || ^7.0.0-0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitest/pretty-format@3.2.4':
|
'@vitest/pretty-format@3.2.4':
|
||||||
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||||
|
|
||||||
'@vitest/runner@3.2.4':
|
'@vitest/pretty-format@4.0.3':
|
||||||
resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
|
resolution: {integrity: sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==}
|
||||||
|
|
||||||
'@vitest/snapshot@3.2.4':
|
'@vitest/runner@4.0.3':
|
||||||
resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
|
resolution: {integrity: sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@4.0.3':
|
||||||
|
resolution: {integrity: sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==}
|
||||||
|
|
||||||
'@vitest/spy@3.2.4':
|
'@vitest/spy@3.2.4':
|
||||||
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
||||||
|
|
||||||
|
'@vitest/spy@4.0.3':
|
||||||
|
resolution: {integrity: sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==}
|
||||||
|
|
||||||
'@vitest/utils@3.2.4':
|
'@vitest/utils@3.2.4':
|
||||||
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||||
|
|
||||||
|
'@vitest/utils@4.0.3':
|
||||||
|
resolution: {integrity: sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==}
|
||||||
|
|
||||||
'@volar/language-core@2.4.23':
|
'@volar/language-core@2.4.23':
|
||||||
resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
|
resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
|
||||||
|
|
||||||
@@ -1494,10 +1534,6 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
cac@6.7.14:
|
|
||||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1528,6 +1564,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
chai@6.2.0:
|
||||||
|
resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
chalk@1.1.3:
|
chalk@1.1.3:
|
||||||
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
|
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1804,8 +1844,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=8.45.0'
|
eslint: '>=8.45.0'
|
||||||
|
|
||||||
eslint-plugin-react-hooks@7.0.0:
|
eslint-plugin-react-hooks@7.0.1:
|
||||||
resolution: {integrity: sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==}
|
resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||||
@@ -1821,12 +1861,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
|
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
|
||||||
|
|
||||||
eslint-plugin-storybook@9.1.13:
|
eslint-plugin-storybook@9.1.15:
|
||||||
resolution: {integrity: sha512-kPuhbtGDiJLB5OLZuwFZAxgzWakNDw64sJtXUPN8g0+VAeXfHyZEmsE28qIIETHxtal71lPKVm8QNnERaJHPJQ==}
|
resolution: {integrity: sha512-c7eHGNoKmLkltdrR+KTwTiXDvXWOm8y9tqwcveN/wV10PeqDKyxLxn/dzVpMUwXd1gvEBdIjEjJfTATf6c53jQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=8'
|
eslint: '>=8'
|
||||||
storybook: ^9.1.13
|
storybook: ^9.1.15
|
||||||
|
|
||||||
eslint-scope@7.2.2:
|
eslint-scope@7.2.2:
|
||||||
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
||||||
@@ -2344,9 +2384,6 @@ packages:
|
|||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
js-tokens@9.0.1:
|
|
||||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2820,6 +2857,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==}
|
resolution: {integrity: sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
react-hook-form@7.71.0:
|
||||||
|
resolution: {integrity: sha512-oFDt/iIFMV9ZfV52waONXzg4xuSlbwKUPvXVH2jumL1me5qFhBMc4knZxuXiZ2+j6h546sYe3ZKJcg/900/iHw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
||||||
react-html-attributes@1.4.6:
|
react-html-attributes@1.4.6:
|
||||||
resolution: {integrity: sha512-uS3MmThNKFH2EZUQQw4k5pIcU7XIr208UE5dktrj/GOH1CMagqxDl4DCLpt3o2l9x+IB5nVYBeN3Cr4IutBXAg==}
|
resolution: {integrity: sha512-uS3MmThNKFH2EZUQQw4k5pIcU7XIr208UE5dktrj/GOH1CMagqxDl4DCLpt3o2l9x+IB5nVYBeN3Cr4IutBXAg==}
|
||||||
|
|
||||||
@@ -3058,8 +3101,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
storybook@9.1.13:
|
storybook@9.1.15:
|
||||||
resolution: {integrity: sha512-G3KZ36EVzXyHds72B/qtWiJnhUpM0xOUeYlDcO9DSHL1bDTv15cW4+upBl+mcBZrDvU838cn7Bv4GpF+O5MCfw==}
|
resolution: {integrity: sha512-es7uDdEwRVVUAt7XLAZZ1hicOq9r4ov5NFeFPpa2YEyAsyHYOCr0CTlHBfslWG6D5EVNWK3kVIIuW8GHB6hEig==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prettier: ^2 || ^3
|
prettier: ^2 || ^3
|
||||||
@@ -3130,9 +3173,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
strip-literal@3.1.0:
|
|
||||||
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
|
||||||
|
|
||||||
sugarss@5.0.1:
|
sugarss@5.0.1:
|
||||||
resolution: {integrity: sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==}
|
resolution: {integrity: sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==}
|
||||||
engines: {node: '>=18.0'}
|
engines: {node: '>=18.0'}
|
||||||
@@ -3181,14 +3221,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
tinypool@1.1.1:
|
|
||||||
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
|
||||||
|
|
||||||
tinyrainbow@2.0.0:
|
tinyrainbow@2.0.0:
|
||||||
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyrainbow@3.0.3:
|
||||||
|
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
tinyspy@4.0.4:
|
tinyspy@4.0.4:
|
||||||
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -3388,11 +3428,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
vite-node@3.2.4:
|
|
||||||
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
|
|
||||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
vite-plugin-dts@4.5.4:
|
vite-plugin-dts@4.5.4:
|
||||||
resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==}
|
resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3410,8 +3445,8 @@ packages:
|
|||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vite@7.1.11:
|
vite@7.1.12:
|
||||||
resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==}
|
resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3450,16 +3485,18 @@ packages:
|
|||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vitest@3.2.4:
|
vitest@4.0.3:
|
||||||
resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
|
resolution: {integrity: sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==}
|
||||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@edge-runtime/vm': '*'
|
'@edge-runtime/vm': '*'
|
||||||
'@types/debug': ^4.1.12
|
'@types/debug': ^4.1.12
|
||||||
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||||
'@vitest/browser': 3.2.4
|
'@vitest/browser-playwright': 4.0.3
|
||||||
'@vitest/ui': 3.2.4
|
'@vitest/browser-preview': 4.0.3
|
||||||
|
'@vitest/browser-webdriverio': 4.0.3
|
||||||
|
'@vitest/ui': 4.0.3
|
||||||
happy-dom: '*'
|
happy-dom: '*'
|
||||||
jsdom: '*'
|
jsdom: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -3469,7 +3506,11 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
'@types/node':
|
'@types/node':
|
||||||
optional: true
|
optional: true
|
||||||
'@vitest/browser':
|
'@vitest/browser-playwright':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser-preview':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser-webdriverio':
|
||||||
optional: true
|
optional: true
|
||||||
'@vitest/ui':
|
'@vitest/ui':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -4165,12 +4206,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@sinclair/typebox': 0.27.8
|
'@sinclair/typebox': 0.27.8
|
||||||
|
|
||||||
'@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
'@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
react-docgen-typescript: 2.4.0(typescript@5.9.3)
|
react-docgen-typescript: 2.4.0(typescript@5.9.3)
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
@@ -4264,6 +4305,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
|
||||||
|
'@mantine/modals@8.3.12(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@mantine/core': 8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@mantine/hooks': 8.3.1(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
|
||||||
'@mantine/notifications@8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
'@mantine/notifications@8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mantine/core': 8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
'@mantine/core': 8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
@@ -4343,7 +4391,7 @@ snapshots:
|
|||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.35': {}
|
'@rolldown/pluginutils@1.0.0-beta.43': {}
|
||||||
|
|
||||||
'@rollup/pluginutils@5.3.0(rollup@4.50.2)':
|
'@rollup/pluginutils@5.3.0(rollup@4.50.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4452,53 +4500,55 @@ snapshots:
|
|||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
'@storybook/builder-vite@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
'@standard-schema/spec@1.0.0': {}
|
||||||
dependencies:
|
|
||||||
'@storybook/csf-plugin': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))
|
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
|
||||||
ts-dedent: 2.2.0
|
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
|
||||||
|
|
||||||
'@storybook/csf-plugin@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))':
|
'@storybook/builder-vite@9.1.15(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
'@storybook/csf-plugin': 9.1.15(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))
|
||||||
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
|
ts-dedent: 2.2.0
|
||||||
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
|
|
||||||
|
'@storybook/csf-plugin@9.1.15(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))':
|
||||||
|
dependencies:
|
||||||
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
unplugin: 1.16.1
|
unplugin: 1.16.1
|
||||||
|
|
||||||
'@storybook/global@5.0.0': {}
|
'@storybook/global@5.0.0': {}
|
||||||
|
|
||||||
'@storybook/react-dom-shim@9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))':
|
'@storybook/react-dom-shim@9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
|
|
||||||
'@storybook/react-vite@9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.50.2)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
'@storybook/react-vite@9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.50.2)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
'@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
'@rollup/pluginutils': 5.3.0(rollup@4.50.2)
|
'@rollup/pluginutils': 5.3.0(rollup@4.50.2)
|
||||||
'@storybook/builder-vite': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
'@storybook/builder-vite': 9.1.15(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
'@storybook/react': 9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)
|
'@storybook/react': 9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)
|
||||||
find-up: 7.0.0
|
find-up: 7.0.0
|
||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-docgen: 8.0.1
|
react-docgen: 8.0.1
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
tsconfig-paths: 4.2.0
|
tsconfig-paths: 4.2.0
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
- rollup
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@storybook/react@9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)':
|
'@storybook/react@9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
'@storybook/react-dom-shim': 9.1.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))
|
'@storybook/react-dom-shim': 9.1.15(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
@@ -4842,11 +4892,11 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react-swc@4.1.0(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
'@vitejs/plugin-react-swc@4.2.0(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-beta.35
|
'@rolldown/pluginutils': 1.0.0-beta.43
|
||||||
'@swc/core': 1.13.5
|
'@swc/core': 1.13.5
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@swc/helpers'
|
- '@swc/helpers'
|
||||||
|
|
||||||
@@ -4858,27 +4908,47 @@ snapshots:
|
|||||||
chai: 5.3.3
|
chai: 5.3.3
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
'@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
'@vitest/expect@4.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@standard-schema/spec': 1.0.0
|
||||||
|
'@types/chai': 5.2.2
|
||||||
|
'@vitest/spy': 4.0.3
|
||||||
|
'@vitest/utils': 4.0.3
|
||||||
|
chai: 6.2.0
|
||||||
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 3.2.4
|
'@vitest/spy': 3.2.4
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
|
|
||||||
|
'@vitest/mocker@4.0.3(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 4.0.3
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.19
|
||||||
|
optionalDependencies:
|
||||||
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
|
|
||||||
'@vitest/pretty-format@3.2.4':
|
'@vitest/pretty-format@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
'@vitest/runner@3.2.4':
|
'@vitest/pretty-format@4.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/utils': 3.2.4
|
tinyrainbow: 3.0.3
|
||||||
pathe: 2.0.3
|
|
||||||
strip-literal: 3.1.0
|
|
||||||
|
|
||||||
'@vitest/snapshot@3.2.4':
|
'@vitest/runner@4.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 3.2.4
|
'@vitest/utils': 4.0.3
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/snapshot@4.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 4.0.3
|
||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
@@ -4886,12 +4956,19 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tinyspy: 4.0.4
|
tinyspy: 4.0.4
|
||||||
|
|
||||||
|
'@vitest/spy@4.0.3': {}
|
||||||
|
|
||||||
'@vitest/utils@3.2.4':
|
'@vitest/utils@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 3.2.4
|
'@vitest/pretty-format': 3.2.4
|
||||||
loupe: 3.2.1
|
loupe: 3.2.1
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/utils@4.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 4.0.3
|
||||||
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@volar/language-core@2.4.23':
|
'@volar/language-core@2.4.23':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/source-map': 2.4.23
|
'@volar/source-map': 2.4.23
|
||||||
@@ -5138,8 +5215,6 @@ snapshots:
|
|||||||
node-releases: 2.0.21
|
node-releases: 2.0.21
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.26.2)
|
update-browserslist-db: 1.1.3(browserslist@4.26.2)
|
||||||
|
|
||||||
cac@6.7.14: {}
|
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -5173,6 +5248,8 @@ snapshots:
|
|||||||
loupe: 3.2.1
|
loupe: 3.2.1
|
||||||
pathval: 2.0.1
|
pathval: 2.0.1
|
||||||
|
|
||||||
|
chai@6.2.0: {}
|
||||||
|
|
||||||
chalk@1.1.3:
|
chalk@1.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 2.2.1
|
ansi-styles: 2.2.1
|
||||||
@@ -5528,7 +5605,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
eslint-plugin-react-hooks@7.0.0(eslint@9.38.0(jiti@2.6.1)):
|
eslint-plugin-react-hooks@7.0.1(eslint@9.38.0(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.28.4
|
'@babel/core': 7.28.4
|
||||||
'@babel/parser': 7.28.4
|
'@babel/parser': 7.28.4
|
||||||
@@ -5565,11 +5642,11 @@ snapshots:
|
|||||||
string.prototype.matchall: 4.0.12
|
string.prototype.matchall: 4.0.12
|
||||||
string.prototype.repeat: 1.0.0
|
string.prototype.repeat: 1.0.0
|
||||||
|
|
||||||
eslint-plugin-storybook@9.1.13(eslint@9.38.0(jiti@2.6.1))(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3):
|
eslint-plugin-storybook@9.1.15(eslint@9.38.0(jiti@2.6.1))(storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.43.0(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
||||||
eslint: 9.38.0(jiti@2.6.1)
|
eslint: 9.38.0(jiti@2.6.1)
|
||||||
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
storybook: 9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
@@ -6161,8 +6238,6 @@ snapshots:
|
|||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-tokens@9.0.1: {}
|
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 1.0.10
|
argparse: 1.0.10
|
||||||
@@ -6638,6 +6713,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
|
|
||||||
|
react-hook-form@7.71.0(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
|
||||||
react-html-attributes@1.4.6:
|
react-html-attributes@1.4.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
html-element-attributes: 1.3.1
|
html-element-attributes: 1.3.1
|
||||||
@@ -6922,13 +7001,13 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
storybook@9.1.15(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
'@testing-library/jest-dom': 6.9.1
|
'@testing-library/jest-dom': 6.9.1
|
||||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
'@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
'@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
'@vitest/spy': 3.2.4
|
'@vitest/spy': 3.2.4
|
||||||
better-opn: 3.0.2
|
better-opn: 3.0.2
|
||||||
esbuild: 0.25.9
|
esbuild: 0.25.9
|
||||||
@@ -7032,10 +7111,6 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
strip-literal@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
js-tokens: 9.0.1
|
|
||||||
|
|
||||||
sugarss@5.0.1(postcss@8.5.6):
|
sugarss@5.0.1(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
@@ -7071,10 +7146,10 @@ snapshots:
|
|||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
tinypool@1.1.1: {}
|
|
||||||
|
|
||||||
tinyrainbow@2.0.0: {}
|
tinyrainbow@2.0.0: {}
|
||||||
|
|
||||||
|
tinyrainbow@3.0.3: {}
|
||||||
|
|
||||||
tinyspy@4.0.4: {}
|
tinyspy@4.0.4: {}
|
||||||
|
|
||||||
tldts-core@7.0.17: {}
|
tldts-core@7.0.17: {}
|
||||||
@@ -7251,28 +7326,7 @@ snapshots:
|
|||||||
|
|
||||||
uuid@11.1.0: {}
|
uuid@11.1.0: {}
|
||||||
|
|
||||||
vite-node@3.2.4(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)):
|
vite-plugin-dts@4.5.4(@types/node@24.9.1)(rollup@4.50.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
||||||
dependencies:
|
|
||||||
cac: 6.7.14
|
|
||||||
debug: 4.4.3
|
|
||||||
es-module-lexer: 1.7.0
|
|
||||||
pathe: 2.0.3
|
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/node'
|
|
||||||
- jiti
|
|
||||||
- less
|
|
||||||
- lightningcss
|
|
||||||
- sass
|
|
||||||
- sass-embedded
|
|
||||||
- stylus
|
|
||||||
- sugarss
|
|
||||||
- supports-color
|
|
||||||
- terser
|
|
||||||
- tsx
|
|
||||||
- yaml
|
|
||||||
|
|
||||||
vite-plugin-dts@4.5.4(@types/node@24.9.1)(rollup@4.50.2)(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/api-extractor': 7.52.13(@types/node@24.9.1)
|
'@microsoft/api-extractor': 7.52.13(@types/node@24.9.1)
|
||||||
'@rollup/pluginutils': 5.3.0(rollup@4.50.2)
|
'@rollup/pluginutils': 5.3.0(rollup@4.50.2)
|
||||||
@@ -7285,24 +7339,24 @@ snapshots:
|
|||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- rollup
|
- rollup
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
globrex: 0.1.2
|
globrex: 0.1.2
|
||||||
tsconfck: 3.1.6(typescript@5.9.3)
|
tsconfck: 3.1.6(typescript@5.9.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)):
|
vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.9
|
esbuild: 0.25.9
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -7316,18 +7370,17 @@ snapshots:
|
|||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
sugarss: 5.0.1(postcss@8.5.6)
|
sugarss: 5.0.1(postcss@8.5.6)
|
||||||
|
|
||||||
vitest@3.2.4(@types/node@24.9.1)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(sugarss@5.0.1(postcss@8.5.6)):
|
vitest@4.0.3(@types/node@24.9.1)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(sugarss@5.0.1(postcss@8.5.6)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.2
|
'@vitest/expect': 4.0.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/mocker': 4.0.3(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
||||||
'@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6)))
|
'@vitest/pretty-format': 4.0.3
|
||||||
'@vitest/pretty-format': 3.2.4
|
'@vitest/runner': 4.0.3
|
||||||
'@vitest/runner': 3.2.4
|
'@vitest/snapshot': 4.0.3
|
||||||
'@vitest/snapshot': 3.2.4
|
'@vitest/spy': 4.0.3
|
||||||
'@vitest/spy': 3.2.4
|
'@vitest/utils': 4.0.3
|
||||||
'@vitest/utils': 3.2.4
|
|
||||||
chai: 5.3.3
|
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
expect-type: 1.2.2
|
expect-type: 1.2.2
|
||||||
magic-string: 0.30.19
|
magic-string: 0.30.19
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
@@ -7336,10 +7389,8 @@ snapshots:
|
|||||||
tinybench: 2.9.0
|
tinybench: 2.9.0
|
||||||
tinyexec: 0.3.2
|
tinyexec: 0.3.2
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
tinypool: 1.1.1
|
tinyrainbow: 3.0.3
|
||||||
tinyrainbow: 2.0.0
|
vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
||||||
vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
|
||||||
vite-node: 3.2.4(@types/node@24.9.1)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))
|
|
||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 24.9.1
|
'@types/node': 24.9.1
|
||||||
|
|||||||
38
src/Form/components/Form.tsx
Normal file
38
src/Form/components/Form.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { type ReactNode } from 'react'
|
||||||
|
import { Card, Stack, LoadingOverlay } from '@mantine/core'
|
||||||
|
import { FormSection } from './FormSection'
|
||||||
|
|
||||||
|
interface FormProps {
|
||||||
|
children: ReactNode
|
||||||
|
loading?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Form: React.FC<FormProps> & {
|
||||||
|
Section: typeof FormSection
|
||||||
|
} = ({ children, loading, ...others }) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
withBorder
|
||||||
|
component={Stack}
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
padding={0}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
shadow="sm"
|
||||||
|
radius="md"
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
<LoadingOverlay visible={loading || false} />
|
||||||
|
{children}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.Section = FormSection
|
||||||
55
src/Form/components/FormLayout.tsx
Normal file
55
src/Form/components/FormLayout.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { type ReactNode } from 'react'
|
||||||
|
import { Modal } from '@mantine/core'
|
||||||
|
import { Form } from './Form'
|
||||||
|
import { FormLayoutStoreProvider, useFormLayoutStore } from '../store/FormLayout.store'
|
||||||
|
import type { RequestType } from '../types'
|
||||||
|
|
||||||
|
interface FormLayoutProps {
|
||||||
|
children: ReactNode
|
||||||
|
dirty?: boolean
|
||||||
|
loading?: boolean
|
||||||
|
onCancel?: () => void
|
||||||
|
onSubmit?: () => void
|
||||||
|
request?: RequestType
|
||||||
|
modal?: boolean
|
||||||
|
modalProps?: any
|
||||||
|
nested?: boolean
|
||||||
|
deleteFormProps?: any
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const LayoutComponent: React.FC<FormLayoutProps> = ({
|
||||||
|
children,
|
||||||
|
modal,
|
||||||
|
modalProps,
|
||||||
|
...others
|
||||||
|
}) => {
|
||||||
|
const { request } = useFormLayoutStore((state) => ({
|
||||||
|
request: state.request,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const modalWidth = request === 'delete' ? 400 : modalProps?.width
|
||||||
|
|
||||||
|
return modal === true ? (
|
||||||
|
<Modal
|
||||||
|
onClose={() => modalProps?.onClose?.()}
|
||||||
|
opened={modalProps?.opened || false}
|
||||||
|
size="auto"
|
||||||
|
withCloseButton={false}
|
||||||
|
centered={request !== 'delete'}
|
||||||
|
{...modalProps}
|
||||||
|
>
|
||||||
|
<div style={{ height: modalProps?.height, width: modalWidth }}>
|
||||||
|
<Form {...others}>{children}</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
) : (
|
||||||
|
<Form {...others}>{children}</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormLayout: React.FC<FormLayoutProps> = (props) => (
|
||||||
|
<FormLayoutStoreProvider {...props}>
|
||||||
|
<LayoutComponent {...props} />
|
||||||
|
</FormLayoutStoreProvider>
|
||||||
|
)
|
||||||
117
src/Form/components/FormSection.tsx
Normal file
117
src/Form/components/FormSection.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import React, { type ReactNode } from 'react'
|
||||||
|
import { Stack, Group, Paper, Button, Title, Box } from '@mantine/core'
|
||||||
|
import { useFormLayoutStore } from '../store/FormLayout.store'
|
||||||
|
|
||||||
|
interface FormSectionProps {
|
||||||
|
type: 'header' | 'body' | 'footer' | 'error'
|
||||||
|
title?: string
|
||||||
|
rightSection?: ReactNode
|
||||||
|
children?: ReactNode
|
||||||
|
buttonTitles?: { submit?: string; cancel?: string }
|
||||||
|
className?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormSection: React.FC<FormSectionProps> = ({
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
rightSection,
|
||||||
|
children,
|
||||||
|
buttonTitles,
|
||||||
|
className,
|
||||||
|
...others
|
||||||
|
}) => {
|
||||||
|
const { onCancel, onSubmit, request, loading } = useFormLayoutStore((state) => ({
|
||||||
|
onCancel: state.onCancel,
|
||||||
|
onSubmit: state.onSubmit,
|
||||||
|
request: state.request,
|
||||||
|
loading: state.loading,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (type === 'header') {
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
justify="space-between"
|
||||||
|
p="md"
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid var(--mantine-color-gray-3)',
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
<Title order={4} size="h5">
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
{rightSection && <Box>{rightSection}</Box>}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'body') {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
gap="md"
|
||||||
|
p="md"
|
||||||
|
style={{ flex: 1, overflow: 'auto' }}
|
||||||
|
className={className}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'footer') {
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
justify="flex-end"
|
||||||
|
gap="xs"
|
||||||
|
p="md"
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--mantine-color-gray-3)',
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{request !== 'view' && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{buttonTitles?.cancel ?? 'Cancel'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={onSubmit}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{buttonTitles?.submit ?? (request === 'delete' ? 'Delete' : 'Save')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'error') {
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
p="sm"
|
||||||
|
m="md"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--mantine-color-red-0)',
|
||||||
|
border: '1px solid var(--mantine-color-red-3)',
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
34
src/Form/components/SuperForm.tsx
Normal file
34
src/Form/components/SuperForm.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { forwardRef, type ReactElement, type Ref } from 'react'
|
||||||
|
import { FormProvider, useForm, type FieldValues } from 'react-hook-form'
|
||||||
|
import { Provider } from '../store/SuperForm.store'
|
||||||
|
import type { SuperFormProps, SuperFormRef } from '../types'
|
||||||
|
import Layout from './SuperFormLayout'
|
||||||
|
import SuperFormPersist from './SuperFormPersist'
|
||||||
|
|
||||||
|
const SuperForm = <T extends FieldValues>(
|
||||||
|
{ useFormProps, gridRef, children, persist, ...others }: SuperFormProps<T>,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const form = useForm<T>({ ...useFormProps })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider {...others}>
|
||||||
|
<FormProvider {...form}>
|
||||||
|
{persist && (
|
||||||
|
<SuperFormPersist storageKey={typeof persist === 'object' ? persist.storageKey : null} />
|
||||||
|
)}
|
||||||
|
<Layout<T> gridRef={gridRef} ref={ref}>
|
||||||
|
{children}
|
||||||
|
</Layout>
|
||||||
|
</FormProvider>
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRSuperForm = forwardRef(SuperForm) as <T extends FieldValues>(
|
||||||
|
props: SuperFormProps<T> & {
|
||||||
|
ref?: Ref<SuperFormRef<T>>
|
||||||
|
}
|
||||||
|
) => ReactElement
|
||||||
|
|
||||||
|
export default FRSuperForm
|
||||||
364
src/Form/components/SuperFormLayout.tsx
Normal file
364
src/Form/components/SuperFormLayout.tsx
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
RefObject,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
type MutableRefObject,
|
||||||
|
type ReactElement,
|
||||||
|
type ReactNode,
|
||||||
|
type Ref,
|
||||||
|
} from 'react'
|
||||||
|
import { useFormContext, useFormState, type FieldValues, type UseFormReturn } from 'react-hook-form'
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Group,
|
||||||
|
List,
|
||||||
|
LoadingOverlay,
|
||||||
|
Paper,
|
||||||
|
Spoiler,
|
||||||
|
Stack,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Transition,
|
||||||
|
} from '@mantine/core'
|
||||||
|
import { IconChevronsLeft, IconChevronsRight } from '@tabler/icons-react'
|
||||||
|
import { useUncontrolled } from '@mantine/hooks'
|
||||||
|
import useRemote from '../hooks/useRemote'
|
||||||
|
import { useStore } from '../store/SuperForm.store'
|
||||||
|
import classes from '../styles/Form.module.css'
|
||||||
|
import { Form } from './Form'
|
||||||
|
import { FormLayout } from './FormLayout'
|
||||||
|
import type { GridRef, SuperFormRef } from '../types'
|
||||||
|
|
||||||
|
const SuperFormLayout = <T extends FieldValues>(
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
gridRef,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode | ((props: UseFormReturn<T, any, undefined>) => React.ReactNode)
|
||||||
|
gridRef?: MutableRefObject<GridRef<any> | null>
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
// Component store State
|
||||||
|
const {
|
||||||
|
layoutProps,
|
||||||
|
meta,
|
||||||
|
nested,
|
||||||
|
onBeforeSubmit,
|
||||||
|
onCancel,
|
||||||
|
onLayoutMounted,
|
||||||
|
onLayoutUnMounted,
|
||||||
|
onResetForm,
|
||||||
|
onSubmit,
|
||||||
|
primeData,
|
||||||
|
request,
|
||||||
|
tableName,
|
||||||
|
value,
|
||||||
|
} = useStore((state) => ({
|
||||||
|
extraButtons: state.extraButtons,
|
||||||
|
layoutProps: state.layoutProps,
|
||||||
|
meta: state.meta,
|
||||||
|
nested: state.nested,
|
||||||
|
onBeforeSubmit: state.onBeforeSubmit,
|
||||||
|
onCancel: state.onCancel,
|
||||||
|
onLayoutMounted: state.onLayoutMounted,
|
||||||
|
onLayoutUnMounted: state.onLayoutUnMounted,
|
||||||
|
onResetForm: state.onResetForm,
|
||||||
|
onSubmit: state.onSubmit,
|
||||||
|
primeData: state.primeData,
|
||||||
|
request: state.request,
|
||||||
|
tableName: state.remote?.tableName,
|
||||||
|
value: state.value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const [_opened, _setOpened] = useUncontrolled({
|
||||||
|
value: layoutProps?.bodyRightSection?.opened,
|
||||||
|
defaultValue: false,
|
||||||
|
onChange: layoutProps?.bodyRightSection?.setOpened,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Component Hooks
|
||||||
|
const form = useFormContext<T, any, undefined>()
|
||||||
|
const formState = useFormState({ control: form.control })
|
||||||
|
|
||||||
|
const { isFetching, mutateAsync, error, queryKey } = useRemote(gridRef)
|
||||||
|
|
||||||
|
// Component variables
|
||||||
|
const formUID = useMemo(() => {
|
||||||
|
return meta?.id ?? uuid()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const requestString = request?.charAt(0).toUpperCase() + request?.slice(1)
|
||||||
|
|
||||||
|
const renderRightSection = (
|
||||||
|
<>
|
||||||
|
<Tooltip label={`${_opened ? 'Close' : 'Open'} Right Section`} withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 12,
|
||||||
|
zIndex: 5,
|
||||||
|
|
||||||
|
display: layoutProps?.bodyRightSection?.hideToggleButton ? 'none' : 'block',
|
||||||
|
}}
|
||||||
|
variant='filled'
|
||||||
|
size='sm'
|
||||||
|
onClick={() => _setOpened(!_opened)}
|
||||||
|
radius='6'
|
||||||
|
m={2}
|
||||||
|
>
|
||||||
|
{_opened ? <IconChevronsRight /> : <IconChevronsLeft />}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Group wrap='nowrap' h='100%' align='flex-start' gap={2} w={'100%'}>
|
||||||
|
<Stack gap={0} h='100%' style={{ flex: 1 }}>
|
||||||
|
{typeof children === 'function' ? children({ ...form }) : children}
|
||||||
|
</Stack>
|
||||||
|
<Transition transition='slide-left' mounted={_opened}>
|
||||||
|
{(transitionStyles) => (
|
||||||
|
<Paper
|
||||||
|
style={transitionStyles}
|
||||||
|
h='100%'
|
||||||
|
w={layoutProps?.bodyRightSection?.w}
|
||||||
|
shadow='xs'
|
||||||
|
radius='xs'
|
||||||
|
mr='xs'
|
||||||
|
mt='xs'
|
||||||
|
ml={0}
|
||||||
|
{...layoutProps?.bodyRightSection?.paperProps}
|
||||||
|
>
|
||||||
|
{layoutProps?.bodyRightSection?.render?.({
|
||||||
|
form,
|
||||||
|
formValue: form.getValues(),
|
||||||
|
isFetching,
|
||||||
|
opened: _opened,
|
||||||
|
queryKey,
|
||||||
|
setOpened: _setOpened,
|
||||||
|
})}
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
</Group>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Component Callback Functions
|
||||||
|
const onFormSubmit = async (data: T | any, closeForm: boolean = true) => {
|
||||||
|
const res: any =
|
||||||
|
typeof onBeforeSubmit === 'function'
|
||||||
|
? await mutateAsync?.(await onBeforeSubmit(data, request, form))
|
||||||
|
: await mutateAsync?.(data)
|
||||||
|
|
||||||
|
if ((tableName?.length ?? 0) > 0) {
|
||||||
|
if (res?.ok || (res?.status >= 200 && res?.status < 300)) {
|
||||||
|
onSubmit?.(res?.data, request, data, form, closeForm)
|
||||||
|
} else {
|
||||||
|
form.setError('root', {
|
||||||
|
message: res.status === 401 ? 'Username or password is incorrect' : res?.error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onSubmit?.(data, request, data, form, closeForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
useEffect(() => {
|
||||||
|
if (request === 'insert') {
|
||||||
|
if (onResetForm) {
|
||||||
|
onResetForm(primeData, form).then((resetData) => {
|
||||||
|
form.reset(resetData)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.reset(primeData)
|
||||||
|
}
|
||||||
|
} else if ((request === 'change' || request === 'delete') && (tableName?.length ?? 0) === 0) {
|
||||||
|
if (onResetForm) {
|
||||||
|
onResetForm(value, form).then((resetData) => {
|
||||||
|
form.reset(resetData)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.reset(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onLayoutMounted?.()
|
||||||
|
return onLayoutUnMounted
|
||||||
|
}, [
|
||||||
|
request,
|
||||||
|
primeData,
|
||||||
|
tableName,
|
||||||
|
value,
|
||||||
|
form.reset,
|
||||||
|
onResetForm,
|
||||||
|
onLayoutMounted,
|
||||||
|
onLayoutUnMounted,
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
(Object.keys(formState.errors)?.length > 0 || error) &&
|
||||||
|
_opened === false &&
|
||||||
|
layoutProps?.showErrorList !== false
|
||||||
|
) {
|
||||||
|
_setOpened(true)
|
||||||
|
}
|
||||||
|
}, [Object.keys(formState.errors)?.length > 0, error, layoutProps?.showErrorList])
|
||||||
|
|
||||||
|
useImperativeHandle<SuperFormRef<T>, SuperFormRef<T>>(ref, () => ({
|
||||||
|
form,
|
||||||
|
mutation: { isFetching, mutateAsync, error },
|
||||||
|
submit: (closeForm: boolean = true, afterSubmit?: (data: T | any) => void) => {
|
||||||
|
return form.handleSubmit(async (data: T | any) => {
|
||||||
|
await onFormSubmit(data, closeForm)
|
||||||
|
afterSubmit?.(data)
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
queryKey,
|
||||||
|
getFormState: () => formState,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
name={formUID}
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
form.handleSubmit((data: T | any) => {
|
||||||
|
onFormSubmit(data)
|
||||||
|
})(e)
|
||||||
|
}}
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
className={request === 'view' ? classes.disabled : ''}
|
||||||
|
>
|
||||||
|
{/* <LoadingOverlay
|
||||||
|
visible={isFetching}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
{layoutProps?.noLayout ? (
|
||||||
|
typeof layoutProps?.bodyRightSection?.render === 'function' ? (
|
||||||
|
renderRightSection
|
||||||
|
) : typeof children === 'function' ? (
|
||||||
|
<>
|
||||||
|
<LoadingOverlay
|
||||||
|
visible={isFetching}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{children({ ...form })}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LoadingOverlay
|
||||||
|
visible={isFetching}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<FormLayout
|
||||||
|
dirty={formState.isDirty}
|
||||||
|
loading={isFetching}
|
||||||
|
onCancel={() => onCancel?.(request)}
|
||||||
|
onSubmit={form.handleSubmit((data: T | any) => {
|
||||||
|
onFormSubmit(data)
|
||||||
|
})}
|
||||||
|
request={request}
|
||||||
|
modal={false}
|
||||||
|
nested={nested}
|
||||||
|
>
|
||||||
|
{!layoutProps?.noHeader && (
|
||||||
|
<Form.Section
|
||||||
|
type='header'
|
||||||
|
title={`${layoutProps?.title || requestString}`}
|
||||||
|
rightSection={layoutProps?.rightSection}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(Object.keys(formState.errors)?.length > 0 || error) && (
|
||||||
|
<Form.Section
|
||||||
|
className={classes.sticky}
|
||||||
|
buttonTitles={layoutProps?.buttonTitles}
|
||||||
|
type='error'
|
||||||
|
>
|
||||||
|
<Title order={6} size='sm' c='red'>
|
||||||
|
{(error?.message?.length ?? 0) > 0
|
||||||
|
? 'Server Error'
|
||||||
|
: 'Required information is incomplete*'}
|
||||||
|
</Title>
|
||||||
|
{(error as any)?.response?.data?.msg ||
|
||||||
|
(error as any)?.response?.data?._error ||
|
||||||
|
error?.message}
|
||||||
|
{layoutProps?.showErrorList !== false && (
|
||||||
|
<Spoiler maxHeight={50} showLabel='Show more' hideLabel='Hide'>
|
||||||
|
<List
|
||||||
|
size='xs'
|
||||||
|
style={{
|
||||||
|
color: 'light-dark(var(--mantine-color-dark-7), var(--mantine-color-gray-2))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getErrorMessages(formState.errors)}
|
||||||
|
</List>
|
||||||
|
</Spoiler>
|
||||||
|
)}
|
||||||
|
</Form.Section>
|
||||||
|
)}
|
||||||
|
{typeof layoutProps?.bodyRightSection?.render === 'function' ? (
|
||||||
|
<Form.Section type='body' {...layoutProps?.bodySectionProps}>
|
||||||
|
{renderRightSection}
|
||||||
|
</Form.Section>
|
||||||
|
) : (
|
||||||
|
<Form.Section type='body' {...layoutProps?.bodySectionProps}>
|
||||||
|
{typeof children === 'function' ? children({ ...form }) : children}
|
||||||
|
</Form.Section>
|
||||||
|
)}
|
||||||
|
{!layoutProps?.noFooter && (
|
||||||
|
<Form.Section
|
||||||
|
className={classes.sticky}
|
||||||
|
buttonTitles={layoutProps?.buttonTitles}
|
||||||
|
type='footer'
|
||||||
|
{...(typeof layoutProps?.footerSectionProps === 'function'
|
||||||
|
? layoutProps?.footerSectionProps(ref as RefObject<SuperFormRef<T>>)
|
||||||
|
: layoutProps?.footerSectionProps)}
|
||||||
|
>
|
||||||
|
{typeof layoutProps?.extraButtons === 'function'
|
||||||
|
? layoutProps?.extraButtons(form)
|
||||||
|
: layoutProps?.extraButtons}
|
||||||
|
</Form.Section>
|
||||||
|
)}
|
||||||
|
</FormLayout>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getErrorMessages = (errors: any): ReactNode | null => {
|
||||||
|
return Object.keys(errors ?? {}).map((key) => {
|
||||||
|
if (typeof errors[key] === 'object' && key !== 'ref') {
|
||||||
|
return getErrorMessages(errors[key])
|
||||||
|
}
|
||||||
|
if (key !== 'message') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <List.Item key={key}>{errors[key]}</List.Item>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRSuperFormLayout = forwardRef(SuperFormLayout) as <T extends FieldValues>(
|
||||||
|
props: {
|
||||||
|
children: React.ReactNode | ((props: UseFormReturn<T, any, undefined>) => React.ReactNode)
|
||||||
|
gridRef?: MutableRefObject<GridRef | null>
|
||||||
|
} & {
|
||||||
|
ref?: Ref<SuperFormRef<T>>
|
||||||
|
}
|
||||||
|
) => ReactElement
|
||||||
|
|
||||||
|
export default FRSuperFormLayout
|
||||||
66
src/Form/components/SuperFormPersist.tsx
Normal file
66
src/Form/components/SuperFormPersist.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useFormContext, useFormState } from 'react-hook-form'
|
||||||
|
import { useDebouncedCallback } from '@mantine/hooks'
|
||||||
|
import useSubscribe from '../hooks/use-subscribe'
|
||||||
|
import { useSuperFormStore } from '../store/SuperForm.store'
|
||||||
|
import { openConfirmModal } from '../utils/openConfirmModal'
|
||||||
|
|
||||||
|
const SuperFormPersist = ({ storageKey }: { storageKey?: string | null }) => {
|
||||||
|
// Component store State
|
||||||
|
const [persistKey, setPersistKey] = useState<string>('')
|
||||||
|
const { isDirty, isReady, isSubmitted } = useFormState()
|
||||||
|
|
||||||
|
const { remote, request } = useSuperFormStore((state) => ({
|
||||||
|
request: state.request,
|
||||||
|
remote: state.remote,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Component Hooks
|
||||||
|
const { reset, setValue } = useFormContext()
|
||||||
|
|
||||||
|
const handleFormChange = useDebouncedCallback(({ values }) => {
|
||||||
|
setPersistKey(() => {
|
||||||
|
const key = `superform-persist-${storageKey?.length > 0 ? storageKey : `${remote?.tableName || 'local'}-${request}-${values[remote?.primaryKey] ?? ''}`}`
|
||||||
|
|
||||||
|
if (!isDirty) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(values))
|
||||||
|
|
||||||
|
return key
|
||||||
|
})
|
||||||
|
}, 250)
|
||||||
|
|
||||||
|
useSubscribe('', handleFormChange)
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
useEffect(() => {
|
||||||
|
if (isReady && persistKey) {
|
||||||
|
const data = window.localStorage.getItem(persistKey)
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubmitted) {
|
||||||
|
window.localStorage.removeItem(persistKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
openConfirmModal(
|
||||||
|
() => {
|
||||||
|
reset(JSON.parse(data))
|
||||||
|
setValue('_dirty', true, { shouldDirty: true })
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
window.localStorage.removeItem(persistKey)
|
||||||
|
},
|
||||||
|
'Do you want to restore the previous data that was not submitted?'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [isReady, isSubmitted, persistKey])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SuperFormPersist
|
||||||
43
src/Form/config/ApiConfig.tsx
Normal file
43
src/Form/config/ApiConfig.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { createContext, useContext, ReactNode, useState } from 'react'
|
||||||
|
|
||||||
|
interface ApiConfigContextValue {
|
||||||
|
apiURL: string
|
||||||
|
setApiURL: (url: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApiConfigContext = createContext<ApiConfigContextValue | null>(null)
|
||||||
|
|
||||||
|
interface ApiConfigProviderProps {
|
||||||
|
children: ReactNode
|
||||||
|
defaultApiURL?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ApiConfigProvider: React.FC<ApiConfigProviderProps> = ({
|
||||||
|
children,
|
||||||
|
defaultApiURL = '',
|
||||||
|
}) => {
|
||||||
|
const [apiURL, setApiURL] = useState(defaultApiURL)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ApiConfigContext.Provider value={{ apiURL, setApiURL }}>
|
||||||
|
{children}
|
||||||
|
</ApiConfigContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useApiConfig = (): ApiConfigContextValue => {
|
||||||
|
const context = useContext(ApiConfigContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useApiConfig must be used within ApiConfigProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get API URL with optional override
|
||||||
|
* @param overrideURL - Optional URL to use instead of context value
|
||||||
|
*/
|
||||||
|
export const useApiURL = (overrideURL?: string): string => {
|
||||||
|
const { apiURL } = useApiConfig()
|
||||||
|
return overrideURL ?? apiURL
|
||||||
|
}
|
||||||
146
src/Form/containers/Drawer/SuperFormDrawer.tsx
Normal file
146
src/Form/containers/Drawer/SuperFormDrawer.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type ReactElement,
|
||||||
|
type Ref,
|
||||||
|
} from 'react'
|
||||||
|
import { IconX } from '@tabler/icons-react'
|
||||||
|
import type { FieldValues } from 'react-hook-form'
|
||||||
|
import { ActionIcon, Drawer } from '@mantine/core'
|
||||||
|
import type { SuperFormDrawerProps, SuperFormDrawerRef, SuperFormRef } from '../../types'
|
||||||
|
import SuperForm from '../../components/SuperForm'
|
||||||
|
import { openConfirmModal } from '../../utils/openConfirmModal'
|
||||||
|
|
||||||
|
const SuperFormDrawer = <T extends FieldValues>(
|
||||||
|
{ drawerProps, noCloseOnSubmit, ...formProps }: SuperFormDrawerProps<T>,
|
||||||
|
ref: Ref<SuperFormDrawerRef<T>>
|
||||||
|
) => {
|
||||||
|
// Component Refs
|
||||||
|
const formRef = useRef<SuperFormRef<T>>(null)
|
||||||
|
const drawerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
// Component store State
|
||||||
|
// Tell drawer that form layout mounted to fix refs
|
||||||
|
const [layoutMounted, setLayoutMounted] = useState(false)
|
||||||
|
|
||||||
|
// Component Callback Functions
|
||||||
|
const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => {
|
||||||
|
formProps?.onSubmit?.(data, request, formData, form, closeForm)
|
||||||
|
|
||||||
|
if (request === 'delete') {
|
||||||
|
drawerProps?.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noCloseOnSubmit) {
|
||||||
|
if (closeForm) {
|
||||||
|
drawerProps?.onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = (request) => {
|
||||||
|
if (formRef?.current?.getFormState().isDirty) {
|
||||||
|
openConfirmModal(() => {
|
||||||
|
drawerProps?.onClose()
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
drawerProps?.onClose()
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLayoutMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(true)
|
||||||
|
formProps?.onLayoutMounted?.()
|
||||||
|
}, [formProps?.onLayoutMounted])
|
||||||
|
|
||||||
|
const onLayoutUnMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(false)
|
||||||
|
formProps?.onLayoutUnMounted?.()
|
||||||
|
}, [formProps?.onLayoutUnMounted])
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
useImperativeHandle<SuperFormDrawerRef<T>, SuperFormDrawerRef<T>>(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
...formRef.current,
|
||||||
|
drawer: drawerRef.current,
|
||||||
|
} as SuperFormDrawerRef<T>),
|
||||||
|
[layoutMounted]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
ref={drawerRef}
|
||||||
|
onClose={onCancel}
|
||||||
|
closeOnClickOutside={false}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Escape' && drawerProps.closeOnEscape !== false) {
|
||||||
|
e.stopPropagation()
|
||||||
|
onCancel(formProps.request)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
overlayProps={{ backgroundOpacity: 0.5, blur: 0.5 }}
|
||||||
|
padding={6}
|
||||||
|
position='right'
|
||||||
|
transitionProps={{
|
||||||
|
transition: 'slide-left',
|
||||||
|
duration: 150,
|
||||||
|
timingFunction: 'linear',
|
||||||
|
}}
|
||||||
|
size={500}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'stretch',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
minHeight: '100px',
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
keepMounted={false}
|
||||||
|
{...drawerProps}
|
||||||
|
closeOnEscape={false}
|
||||||
|
withCloseButton={false}
|
||||||
|
title={null}
|
||||||
|
>
|
||||||
|
<SuperForm<T>
|
||||||
|
{...formProps}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onLayoutMounted={onLayoutMounted}
|
||||||
|
onLayoutUnMounted={onLayoutUnMounted}
|
||||||
|
ref={formRef}
|
||||||
|
layoutProps={{
|
||||||
|
...formProps?.layoutProps,
|
||||||
|
rightSection: (
|
||||||
|
<ActionIcon
|
||||||
|
size='xs'
|
||||||
|
onClick={() => {
|
||||||
|
onCancel(formProps?.request)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconX size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
),
|
||||||
|
title:
|
||||||
|
(drawerProps.title as string) ??
|
||||||
|
formProps?.layoutProps?.title ??
|
||||||
|
(formProps?.request as string),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRSuperFormDrawer = forwardRef(SuperFormDrawer) as <T extends FieldValues>(
|
||||||
|
props: SuperFormDrawerProps<T> & { ref?: Ref<SuperFormDrawerRef<T>> }
|
||||||
|
) => ReactElement
|
||||||
|
|
||||||
|
export default FRSuperFormDrawer
|
||||||
105
src/Form/containers/Modal/SuperFormModal.tsx
Normal file
105
src/Form/containers/Modal/SuperFormModal.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type ReactElement,
|
||||||
|
type Ref,
|
||||||
|
} from 'react'
|
||||||
|
import type { FieldValues } from 'react-hook-form'
|
||||||
|
import { Modal, ScrollArea } from '@mantine/core'
|
||||||
|
import type { SuperFormModalProps, SuperFormModalRef, SuperFormRef } from '../../types'
|
||||||
|
import SuperForm from '../../components/SuperForm'
|
||||||
|
import { openConfirmModal } from '../../utils/openConfirmModal'
|
||||||
|
|
||||||
|
const SuperFormModal = <T extends FieldValues>(
|
||||||
|
{ modalProps, noCloseOnSubmit, ...formProps }: SuperFormModalProps<T>,
|
||||||
|
ref: Ref<SuperFormModalRef<T>>
|
||||||
|
) => {
|
||||||
|
// Component Refs
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null)
|
||||||
|
const formRef = useRef<SuperFormRef<T>>(null)
|
||||||
|
|
||||||
|
// Component store State
|
||||||
|
// Tell drawer that form layout mounted to fix refs
|
||||||
|
const [layoutMounted, setLayoutMounted] = useState(false)
|
||||||
|
|
||||||
|
// Component Callback Functions
|
||||||
|
const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => {
|
||||||
|
formProps?.onSubmit?.(data, request, formData, form, closeForm)
|
||||||
|
|
||||||
|
if (request === 'delete') {
|
||||||
|
modalProps?.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noCloseOnSubmit) {
|
||||||
|
if (closeForm) {
|
||||||
|
modalProps?.onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = (request) => {
|
||||||
|
if (formRef?.current?.getFormState().isDirty) {
|
||||||
|
openConfirmModal(() => {
|
||||||
|
modalProps?.onClose()
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
modalProps?.onClose()
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLayoutMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(true)
|
||||||
|
formProps?.onLayoutMounted?.()
|
||||||
|
}, [formProps?.onLayoutMounted])
|
||||||
|
|
||||||
|
const onLayoutUnMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(false)
|
||||||
|
formProps?.onLayoutUnMounted?.()
|
||||||
|
}, [formProps?.onLayoutUnMounted])
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
useImperativeHandle<SuperFormModalRef<T>, SuperFormModalRef<T>>(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
...formRef.current,
|
||||||
|
modal: modalRef.current,
|
||||||
|
} as SuperFormModalRef<T>),
|
||||||
|
[layoutMounted]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
ref={modalRef}
|
||||||
|
closeOnClickOutside={false}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
blur: 4,
|
||||||
|
}}
|
||||||
|
padding='sm'
|
||||||
|
scrollAreaComponent={ScrollArea.Autosize}
|
||||||
|
size={500}
|
||||||
|
keepMounted={false}
|
||||||
|
{...modalProps}
|
||||||
|
>
|
||||||
|
<SuperForm<T>
|
||||||
|
{...formProps}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onLayoutMounted={onLayoutMounted}
|
||||||
|
onLayoutUnMounted={onLayoutUnMounted}
|
||||||
|
ref={formRef}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRSuperFormModal = forwardRef(SuperFormModal) as <T extends FieldValues>(
|
||||||
|
props: SuperFormModalProps<T> & { ref?: Ref<SuperFormModalRef<T>> }
|
||||||
|
) => ReactElement
|
||||||
|
|
||||||
|
export default FRSuperFormModal
|
||||||
116
src/Form/containers/Popover/SuperFormPopover.tsx
Normal file
116
src/Form/containers/Popover/SuperFormPopover.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type ReactElement,
|
||||||
|
type Ref,
|
||||||
|
} from 'react'
|
||||||
|
import type { FieldValues } from 'react-hook-form'
|
||||||
|
import { Box, Popover } from '@mantine/core'
|
||||||
|
import { useUncontrolled } from '@mantine/hooks'
|
||||||
|
import type { SuperFormPopoverProps, SuperFormPopoverRef, SuperFormRef } from '../../types'
|
||||||
|
import SuperForm from '../../components/SuperForm'
|
||||||
|
import { openConfirmModal } from '../../utils/openConfirmModal'
|
||||||
|
|
||||||
|
const SuperFormPopover = <T extends FieldValues>(
|
||||||
|
{ popoverProps, target, noCloseOnSubmit, ...formProps }: SuperFormPopoverProps<T>,
|
||||||
|
ref: Ref<SuperFormPopoverRef<T>>
|
||||||
|
) => {
|
||||||
|
// Component Refs
|
||||||
|
const popoverRef = useRef<HTMLDivElement>(null)
|
||||||
|
const formRef = useRef<SuperFormRef<T>>(null)
|
||||||
|
|
||||||
|
// Component store State
|
||||||
|
// Tell drawer that form layout mounted to fix refs
|
||||||
|
const [layoutMounted, setLayoutMounted] = useState(false)
|
||||||
|
|
||||||
|
// Component Hooks
|
||||||
|
const [_value, _onChange] = useUncontrolled({
|
||||||
|
value: popoverProps?.opened,
|
||||||
|
onChange: popoverProps?.onChange,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Component Callback Functions
|
||||||
|
const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => {
|
||||||
|
formProps?.onSubmit?.(data, request, formData, form, closeForm)
|
||||||
|
|
||||||
|
if (request === 'delete') {
|
||||||
|
_onChange(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noCloseOnSubmit) {
|
||||||
|
if (closeForm) {
|
||||||
|
_onChange(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = (request) => {
|
||||||
|
if (formRef?.current?.getFormState().isDirty) {
|
||||||
|
openConfirmModal(() => {
|
||||||
|
_onChange(false)
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_onChange(false)
|
||||||
|
formProps?.onCancel?.(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLayoutMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(true)
|
||||||
|
formProps?.onLayoutMounted?.()
|
||||||
|
}, [formProps?.onLayoutMounted])
|
||||||
|
|
||||||
|
const onLayoutUnMounted = useCallback(() => {
|
||||||
|
setLayoutMounted(false)
|
||||||
|
formProps?.onLayoutUnMounted?.()
|
||||||
|
}, [formProps?.onLayoutUnMounted])
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
useImperativeHandle<SuperFormPopoverRef<T>, SuperFormPopoverRef<T>>(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
...formRef.current,
|
||||||
|
popover: popoverRef.current,
|
||||||
|
} as SuperFormPopoverRef<T>),
|
||||||
|
[layoutMounted]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
closeOnClickOutside={false}
|
||||||
|
onClose={() => _onChange(false)}
|
||||||
|
opened={_value}
|
||||||
|
position='left'
|
||||||
|
radius='md'
|
||||||
|
withArrow
|
||||||
|
withinPortal
|
||||||
|
zIndex={200}
|
||||||
|
keepMounted={false}
|
||||||
|
{...popoverProps}
|
||||||
|
>
|
||||||
|
<Popover.Target>
|
||||||
|
<Box onClick={() => _onChange(true)}>{target}</Box>
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown p={0} m={0} ref={popoverRef}>
|
||||||
|
<SuperForm
|
||||||
|
{...formProps}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onLayoutMounted={onLayoutMounted}
|
||||||
|
onLayoutUnMounted={onLayoutUnMounted}
|
||||||
|
ref={formRef}
|
||||||
|
/>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRSuperFormPopover = forwardRef(SuperFormPopover) as <T extends FieldValues>(
|
||||||
|
props: SuperFormPopoverProps<T> & { ref?: Ref<SuperFormPopoverRef<T>> }
|
||||||
|
) => ReactElement
|
||||||
|
|
||||||
|
export default FRSuperFormPopover
|
||||||
96
src/Form/hooks/use-drawer-form-state.tsx
Normal file
96
src/Form/hooks/use-drawer-form-state.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FieldValues } from 'react-hook-form'
|
||||||
|
import { SuperFormProps, RequestType, ExtendedDrawerProps } from '../types'
|
||||||
|
|
||||||
|
interface UseDrawerFormState<T extends FieldValues> extends Partial<SuperFormProps<T>> {
|
||||||
|
drawerProps: Partial<ExtendedDrawerProps>
|
||||||
|
opened?: boolean
|
||||||
|
onClose?: () => void
|
||||||
|
request: RequestType
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AskFunction = (request: RequestType, buffer: any) => void
|
||||||
|
|
||||||
|
const useDrawerFormState = <T extends FieldValues>(
|
||||||
|
props?: Partial<UseDrawerFormState<T>>
|
||||||
|
): {
|
||||||
|
formProps: UseDrawerFormState<T>
|
||||||
|
setFormProps: React.Dispatch<React.SetStateAction<UseDrawerFormState<T>>>
|
||||||
|
open: (props: Partial<UseDrawerFormState<T>>) => void
|
||||||
|
close: () => void
|
||||||
|
ask: AskFunction
|
||||||
|
} => {
|
||||||
|
const [formProps, setFormProps] = useState<UseDrawerFormState<T>>({
|
||||||
|
opened: false,
|
||||||
|
request: 'insert',
|
||||||
|
...props,
|
||||||
|
onClose: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
drawerProps: { ...curr.drawerProps, opened: false },
|
||||||
|
})),
|
||||||
|
drawerProps: { opened: false, onClose: () => {}, ...props?.drawerProps },
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formProps,
|
||||||
|
setFormProps,
|
||||||
|
open: (props?: Partial<UseDrawerFormState<T>>) => {
|
||||||
|
setFormProps((curr) => {
|
||||||
|
return {
|
||||||
|
...curr,
|
||||||
|
...props,
|
||||||
|
request: props.request ?? curr.request,
|
||||||
|
opened: true,
|
||||||
|
drawerProps: {
|
||||||
|
...curr.drawerProps,
|
||||||
|
...props?.drawerProps,
|
||||||
|
opened: true,
|
||||||
|
onClose: curr.onClose,
|
||||||
|
},
|
||||||
|
primeData: props?.primeData,
|
||||||
|
useFormProps: {
|
||||||
|
...curr.useFormProps,
|
||||||
|
...props?.useFormProps,
|
||||||
|
},
|
||||||
|
layoutProps: {
|
||||||
|
...curr.layoutProps,
|
||||||
|
...props?.layoutProps,
|
||||||
|
},
|
||||||
|
useQueryOptions: {
|
||||||
|
...curr.useQueryOptions,
|
||||||
|
...props?.useQueryOptions,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
...curr.meta,
|
||||||
|
...props?.meta,
|
||||||
|
},
|
||||||
|
useMutationOptions: {
|
||||||
|
...curr.useMutationOptions,
|
||||||
|
...props?.useMutationOptions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
drawerProps: { ...curr.drawerProps, opened: false, onClose: curr.onClose },
|
||||||
|
})),
|
||||||
|
ask: (request: RequestType, buffer: any) => {
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
request,
|
||||||
|
value: buffer,
|
||||||
|
opened: true,
|
||||||
|
drawerProps: { ...curr.drawerProps, opened: true, onClose: curr.onClose },
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useDrawerFormState
|
||||||
|
export type { UseDrawerFormState }
|
||||||
97
src/Form/hooks/use-modal-form-state.tsx
Normal file
97
src/Form/hooks/use-modal-form-state.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FieldValues } from 'react-hook-form'
|
||||||
|
import { ModalProps } from '@mantine/core'
|
||||||
|
import { SuperFormProps, RequestType } from '../types'
|
||||||
|
|
||||||
|
interface UseModalFormState<T extends FieldValues> extends Partial<SuperFormProps<T>> {
|
||||||
|
modalProps: ModalProps
|
||||||
|
opened?: boolean
|
||||||
|
onClose?: () => void
|
||||||
|
request: RequestType
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AskFunction = (request: RequestType, buffer: any) => void
|
||||||
|
|
||||||
|
const useModalFormState = <T extends FieldValues>(
|
||||||
|
props?: Partial<UseModalFormState<T>>
|
||||||
|
): {
|
||||||
|
formProps: UseModalFormState<T>
|
||||||
|
setFormProps: React.Dispatch<React.SetStateAction<UseModalFormState<T>>>
|
||||||
|
open: (props: Partial<UseModalFormState<T>>) => void
|
||||||
|
close: () => void
|
||||||
|
ask: AskFunction
|
||||||
|
} => {
|
||||||
|
const [formProps, setFormProps] = useState<UseModalFormState<T>>({
|
||||||
|
opened: false,
|
||||||
|
request: 'insert',
|
||||||
|
...props,
|
||||||
|
onClose: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
modalProps: { ...curr.modalProps, opened: false },
|
||||||
|
})),
|
||||||
|
modalProps: { opened: false, onClose: () => {}, ...props?.modalProps },
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formProps,
|
||||||
|
setFormProps,
|
||||||
|
open: (props?: Partial<UseModalFormState<T>>) => {
|
||||||
|
setFormProps((curr) => {
|
||||||
|
return {
|
||||||
|
...curr,
|
||||||
|
...props,
|
||||||
|
request: props.request ?? curr.request,
|
||||||
|
opened: true,
|
||||||
|
modalProps: {
|
||||||
|
...curr.modalProps,
|
||||||
|
...props?.modalProps,
|
||||||
|
opened: true,
|
||||||
|
onClose: curr.onClose,
|
||||||
|
},
|
||||||
|
primeData: props?.primeData,
|
||||||
|
useFormProps: {
|
||||||
|
...curr.useFormProps,
|
||||||
|
...props?.useFormProps,
|
||||||
|
},
|
||||||
|
layoutProps: {
|
||||||
|
...curr.layoutProps,
|
||||||
|
...props?.layoutProps,
|
||||||
|
},
|
||||||
|
useQueryOptions: {
|
||||||
|
...curr.useQueryOptions,
|
||||||
|
...props?.useQueryOptions,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
...curr.meta,
|
||||||
|
...props?.meta,
|
||||||
|
},
|
||||||
|
useMutationOptions: {
|
||||||
|
...curr.useMutationOptions,
|
||||||
|
...props?.useMutationOptions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
modalProps: { ...curr.modalProps, opened: false, onClose: curr.onClose },
|
||||||
|
})),
|
||||||
|
ask: (request: RequestType, buffer: any) => {
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
request,
|
||||||
|
value: buffer,
|
||||||
|
opened: true,
|
||||||
|
modalProps: { ...curr.modalProps, opened: true, onClose: curr.onClose },
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useModalFormState
|
||||||
|
export type { UseModalFormState }
|
||||||
97
src/Form/hooks/use-popover-form-state.tsx
Normal file
97
src/Form/hooks/use-popover-form-state.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FieldValues } from 'react-hook-form'
|
||||||
|
import { PopoverProps } from '@mantine/core'
|
||||||
|
import { SuperFormProps, RequestType } from '../types'
|
||||||
|
|
||||||
|
interface UsePopoverFormState<T extends FieldValues> extends Partial<SuperFormProps<T>> {
|
||||||
|
popoverProps: Omit<PopoverProps, 'children'>
|
||||||
|
opened?: boolean
|
||||||
|
onClose?: () => void
|
||||||
|
request: RequestType
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AskFunction = (request: RequestType, buffer: any) => void
|
||||||
|
|
||||||
|
const usePopoverFormState = <T extends FieldValues>(
|
||||||
|
props?: Partial<UsePopoverFormState<T>>
|
||||||
|
): {
|
||||||
|
formProps: UsePopoverFormState<T>
|
||||||
|
setFormProps: React.Dispatch<React.SetStateAction<UsePopoverFormState<T>>>
|
||||||
|
open: (props: Partial<UsePopoverFormState<T>>) => void
|
||||||
|
close: () => void
|
||||||
|
ask: AskFunction
|
||||||
|
} => {
|
||||||
|
const [formProps, setFormProps] = useState<UsePopoverFormState<T>>({
|
||||||
|
opened: false,
|
||||||
|
request: 'insert',
|
||||||
|
...props,
|
||||||
|
popoverProps: { opened: false, onClose: () => {}, ...props?.popoverProps },
|
||||||
|
onClose: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
popoverProps: { ...curr.popoverProps, opened: false },
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formProps,
|
||||||
|
setFormProps,
|
||||||
|
open: (props?: Partial<UsePopoverFormState<T>>) => {
|
||||||
|
setFormProps((curr) => {
|
||||||
|
return {
|
||||||
|
...curr,
|
||||||
|
...props,
|
||||||
|
request: props.request ?? curr.request,
|
||||||
|
opened: true,
|
||||||
|
popoverProps: {
|
||||||
|
...curr.popoverProps,
|
||||||
|
...props?.popoverProps,
|
||||||
|
opened: true,
|
||||||
|
onClose: curr.onClose,
|
||||||
|
},
|
||||||
|
primeData: props?.primeData,
|
||||||
|
useFormProps: {
|
||||||
|
...curr.useFormProps,
|
||||||
|
...props?.useFormProps,
|
||||||
|
},
|
||||||
|
layoutProps: {
|
||||||
|
...curr.layoutProps,
|
||||||
|
...props?.layoutProps,
|
||||||
|
},
|
||||||
|
useQueryOptions: {
|
||||||
|
...curr.useQueryOptions,
|
||||||
|
...props?.useQueryOptions,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
...curr.meta,
|
||||||
|
...props?.meta,
|
||||||
|
},
|
||||||
|
useMutationOptions: {
|
||||||
|
...curr.useMutationOptions,
|
||||||
|
...props?.useMutationOptions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close: () =>
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
opened: false,
|
||||||
|
popoverProps: { ...curr.popoverProps, opened: false, onClose: curr.onClose },
|
||||||
|
})),
|
||||||
|
ask: (request: RequestType, buffer: any) => {
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
request,
|
||||||
|
value: buffer,
|
||||||
|
opened: true,
|
||||||
|
popoverProps: { ...curr.popoverProps, opened: true, onClose: curr.onClose },
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default usePopoverFormState
|
||||||
|
export type { UsePopoverFormState }
|
||||||
40
src/Form/hooks/use-subscribe.tsx
Normal file
40
src/Form/hooks/use-subscribe.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import {
|
||||||
|
EventType,
|
||||||
|
FieldValues,
|
||||||
|
FormState,
|
||||||
|
InternalFieldName,
|
||||||
|
Path,
|
||||||
|
ReadFormState,
|
||||||
|
useFormContext,
|
||||||
|
UseFormReturn,
|
||||||
|
} from 'react-hook-form'
|
||||||
|
|
||||||
|
const useSubscribe = <T extends FieldValues>(
|
||||||
|
name: Path<T> | readonly Path<T>[] | undefined,
|
||||||
|
callback: (
|
||||||
|
data: Partial<FormState<T>> & {
|
||||||
|
values: FieldValues
|
||||||
|
name?: InternalFieldName
|
||||||
|
type?: EventType
|
||||||
|
},
|
||||||
|
form?: UseFormReturn<T, any, T>
|
||||||
|
) => void,
|
||||||
|
formState?: ReadFormState,
|
||||||
|
deps?: unknown[]
|
||||||
|
) => {
|
||||||
|
const form = useFormContext<T>()
|
||||||
|
|
||||||
|
return useEffect(() => {
|
||||||
|
const unsubscribe = form.subscribe({
|
||||||
|
name,
|
||||||
|
callback: (data) => callback(data, form),
|
||||||
|
formState: { values: true, ...formState },
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return unsubscribe
|
||||||
|
}, [form.subscribe, ...(deps || [])])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSubscribe
|
||||||
38
src/Form/hooks/use-super-form-state.tsx
Normal file
38
src/Form/hooks/use-super-form-state.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FieldValues } from 'react-hook-form'
|
||||||
|
import { SuperFormProps, RequestType } from '../types'
|
||||||
|
|
||||||
|
interface UseSuperFormState<T extends FieldValues> extends Partial<SuperFormProps<T>> {
|
||||||
|
request: RequestType
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AskFunction = (request: RequestType, buffer: any) => void
|
||||||
|
|
||||||
|
const useSuperFormState = <T extends FieldValues>(
|
||||||
|
props?: UseSuperFormState<T>
|
||||||
|
): {
|
||||||
|
formProps: UseSuperFormState<any>
|
||||||
|
setFormProps: React.Dispatch<React.SetStateAction<UseSuperFormState<T>>>
|
||||||
|
ask: AskFunction
|
||||||
|
} => {
|
||||||
|
const [formProps, setFormProps] = useState<UseSuperFormState<T>>({
|
||||||
|
request: 'insert',
|
||||||
|
...props,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formProps,
|
||||||
|
setFormProps,
|
||||||
|
ask: (request: RequestType, buffer: any) => {
|
||||||
|
setFormProps((curr) => ({
|
||||||
|
...curr,
|
||||||
|
request,
|
||||||
|
value: buffer,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSuperFormState
|
||||||
|
export type { UseSuperFormState }
|
||||||
123
src/Form/hooks/useRemote.tsx
Normal file
123
src/Form/hooks/useRemote.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { useEffect, type MutableRefObject } from 'react'
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { useFormContext, useFormState, type FieldValues } from 'react-hook-form'
|
||||||
|
import { useStore } from '../store/SuperForm.store'
|
||||||
|
import { useApiURL } from '../config/ApiConfig'
|
||||||
|
import { getNestedValue } from '../utils/getNestedValue'
|
||||||
|
import { fetchClient, type FetchResponse, FetchError } from '../utils/fetchClient'
|
||||||
|
import type { GridRef } from '../types'
|
||||||
|
|
||||||
|
const useRemote = <T extends FieldValues>(gridRef?: MutableRefObject<GridRef<any> | null>) => {
|
||||||
|
// Component store State
|
||||||
|
const { onResetForm, remote, request, useMutationOptions, useQueryOptions, value } = useStore(
|
||||||
|
(state) => ({
|
||||||
|
onResetForm: state.onResetForm,
|
||||||
|
remote: state.remote,
|
||||||
|
request: state.request,
|
||||||
|
useMutationOptions: state.useMutationOptions,
|
||||||
|
useQueryOptions: state.useQueryOptions,
|
||||||
|
value: state.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Component Hooks
|
||||||
|
const form = useFormContext<T>()
|
||||||
|
const { isDirty } = useFormState({ control: form.control })
|
||||||
|
|
||||||
|
// Component use Effects
|
||||||
|
const qc = useQueryClient()
|
||||||
|
|
||||||
|
// Get API URL from context or override
|
||||||
|
const contextApiURL = useApiURL()
|
||||||
|
|
||||||
|
const id = remote?.primaryKey?.includes('.')
|
||||||
|
? getNestedValue(remote?.primaryKey, value)
|
||||||
|
: value?.[remote?.primaryKey ?? '']
|
||||||
|
|
||||||
|
const queryKey = useQueryOptions?.queryKey || [remote?.tableName, id]
|
||||||
|
|
||||||
|
const enabled =
|
||||||
|
useQueryOptions?.enabled ||
|
||||||
|
!!(
|
||||||
|
(remote?.enabled ?? true) &&
|
||||||
|
(remote?.tableName ?? '').length > 0 &&
|
||||||
|
(request === 'change' || request === 'delete') &&
|
||||||
|
String(id) !== 'undefined' &&
|
||||||
|
(String(id)?.length ?? 0) > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
let url = remote?.apiURL ?? `${contextApiURL}/${remote?.tableName}`
|
||||||
|
url = url?.endsWith('/') ? url.substring(0, url.length - 1) : url
|
||||||
|
|
||||||
|
const { isSuccess, status, data, isFetching } = useQuery<FetchResponse<T>>({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => fetchClient.get<T>(`${url}/${id}`, remote?.apiOptions),
|
||||||
|
enabled,
|
||||||
|
refetchOnMount: 'always',
|
||||||
|
refetchOnReconnect: !isDirty,
|
||||||
|
refetchOnWindowFocus: !isDirty,
|
||||||
|
staleTime: 0,
|
||||||
|
gcTime: 0,
|
||||||
|
...useQueryOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeMut = useMutation({
|
||||||
|
// @ts-ignore
|
||||||
|
mutationFn: (mutVal: T) => {
|
||||||
|
if (!remote?.tableName || !remote?.primaryKey) {
|
||||||
|
return Promise.resolve(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request === 'insert'
|
||||||
|
? fetchClient.post(url, mutVal, remote?.apiOptions)
|
||||||
|
: request === 'change'
|
||||||
|
? fetchClient.post(`${url}/${id}`, mutVal, remote?.apiOptions)
|
||||||
|
: request === 'delete'
|
||||||
|
? fetchClient.delete(`${url}/${id}`, remote?.apiOptions)
|
||||||
|
: Promise.resolve(null)
|
||||||
|
},
|
||||||
|
onSettled: (response: FetchResponse | null) => {
|
||||||
|
qc?.invalidateQueries({ queryKey: [remote?.tableName] })
|
||||||
|
|
||||||
|
if (request !== 'delete' && response) {
|
||||||
|
if (onResetForm) {
|
||||||
|
onResetForm(response?.data, form).then(() => {
|
||||||
|
form.reset(response?.data, { keepDirty: false })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.reset(response?.data, { keepDirty: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gridRef?.current?.refresh?.()
|
||||||
|
// @ts-ignore
|
||||||
|
gridRef?.current?.selectRow?.(response?.data?.[remote?.primaryKey ?? ''])
|
||||||
|
},
|
||||||
|
...useMutationOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess && status === 'success' && enabled && !isFetching) {
|
||||||
|
if (!Object.keys(data?.data ?? {}).includes(remote?.primaryKey ?? '')) {
|
||||||
|
throw new Error('Primary key not found in remote data')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onResetForm) {
|
||||||
|
onResetForm(data?.data, form).then((resetData) => {
|
||||||
|
form.reset(resetData)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.reset(data?.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isSuccess, status, enabled, isFetching])
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: changeMut.error as FetchError,
|
||||||
|
isFetching: (enabled ? isFetching : false) || changeMut?.isPending,
|
||||||
|
mutateAsync: changeMut.mutateAsync,
|
||||||
|
queryKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useRemote
|
||||||
48
src/Form/store/FormLayout.store.tsx
Normal file
48
src/Form/store/FormLayout.store.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React, { createContext, useContext, type ReactNode } from 'react'
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import type { RequestType } from '../types'
|
||||||
|
|
||||||
|
interface FormLayoutState {
|
||||||
|
request: RequestType
|
||||||
|
loading: boolean
|
||||||
|
dirty: boolean
|
||||||
|
onCancel?: () => void
|
||||||
|
onSubmit?: () => void
|
||||||
|
setState: (key: string, value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFormLayoutStore = (initialProps: any) =>
|
||||||
|
create<FormLayoutState>((set) => ({
|
||||||
|
request: initialProps.request || 'insert',
|
||||||
|
loading: initialProps.loading || false,
|
||||||
|
dirty: initialProps.dirty || false,
|
||||||
|
onCancel: initialProps.onCancel,
|
||||||
|
onSubmit: initialProps.onSubmit,
|
||||||
|
setState: (key, value) => set({ [key]: value }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const FormLayoutStoreContext = createContext<ReturnType<typeof createFormLayoutStore> | null>(null)
|
||||||
|
|
||||||
|
export const FormLayoutStoreProvider: React.FC<{ children: ReactNode; [key: string]: any }> = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const storeRef = React.useRef<ReturnType<typeof createFormLayoutStore>>()
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = createFormLayoutStore(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormLayoutStoreContext.Provider value={storeRef.current}>
|
||||||
|
{children}
|
||||||
|
</FormLayoutStoreContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFormLayoutStore = <T,>(selector: (state: FormLayoutState) => T): T => {
|
||||||
|
const store = useContext(FormLayoutStoreContext)
|
||||||
|
if (!store) {
|
||||||
|
throw new Error('useFormLayoutStore must be used within FormLayoutStoreProvider')
|
||||||
|
}
|
||||||
|
return store(selector)
|
||||||
|
}
|
||||||
22
src/Form/store/SuperForm.store.tsx
Normal file
22
src/Form/store/SuperForm.store.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { SuperFormProviderProps } from '../types'
|
||||||
|
import { createSyncStore } from '@warkypublic/zustandsyncstore'
|
||||||
|
|
||||||
|
const { Provider, useStore } = createSyncStore<any, SuperFormProviderProps>((set) => ({
|
||||||
|
request: 'insert',
|
||||||
|
setRequest: (request) => {
|
||||||
|
set({ request })
|
||||||
|
},
|
||||||
|
|
||||||
|
value: undefined,
|
||||||
|
setValue: (value) => {
|
||||||
|
set({ value })
|
||||||
|
},
|
||||||
|
|
||||||
|
noCloseOnSubmit: false,
|
||||||
|
setNoCloseOnSubmit: (noCloseOnSubmit) => {
|
||||||
|
set({ noCloseOnSubmit })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export { Provider, useStore }
|
||||||
|
export const useSuperFormStore = useStore
|
||||||
10
src/Form/styles/Form.module.css
Normal file
10
src/Form/styles/Form.module.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
135
src/Form/types/form.types.ts
Normal file
135
src/Form/types/form.types.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import type { UseMutationOptions, UseMutationResult, UseQueryOptions } from '@tanstack/react-query'
|
||||||
|
import type { FieldValues, UseFormProps, UseFormReturn, UseFormStateReturn } from 'react-hook-form'
|
||||||
|
import type { ModalProps, PaperProps, PopoverProps, DrawerProps } from '@mantine/core'
|
||||||
|
import type { RemoteConfig } from './remote.types'
|
||||||
|
|
||||||
|
export type RequestType = 'insert' | 'change' | 'view' | 'select' | 'delete' | 'get' | 'set'
|
||||||
|
|
||||||
|
// Grid integration types (simplified - removes BTGlideRef dependency)
|
||||||
|
export interface GridRef<T = any> {
|
||||||
|
refresh?: () => void
|
||||||
|
selectRow?: (id: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormSectionBodyProps {
|
||||||
|
// Add properties as needed from original FormLayout
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormSectionFooterProps {
|
||||||
|
// Add properties as needed from original FormLayout
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BodyRightSection<T extends FieldValues> {
|
||||||
|
opened?: boolean
|
||||||
|
setOpened?: (opened: boolean) => void
|
||||||
|
w: number | string
|
||||||
|
hideToggleButton?: boolean
|
||||||
|
paperProps?: PaperProps
|
||||||
|
render: (props: {
|
||||||
|
form: UseFormReturn<any, any, undefined>
|
||||||
|
formValue: T
|
||||||
|
isFetching: boolean
|
||||||
|
opened: boolean
|
||||||
|
queryKey: any
|
||||||
|
setOpened: (opened: boolean) => void
|
||||||
|
}) => React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormLayoutProps<T extends FieldValues> {
|
||||||
|
buttonTitles?: { submit?: string; cancel?: string }
|
||||||
|
extraButtons?: React.ReactNode | ((form: UseFormReturn<any, any, undefined>) => React.ReactNode)
|
||||||
|
noFooter?: boolean
|
||||||
|
noHeader?: boolean
|
||||||
|
noLayout?: boolean
|
||||||
|
bodySectionProps?: Partial<FormSectionBodyProps>
|
||||||
|
footerSectionProps?:
|
||||||
|
| Partial<FormSectionFooterProps>
|
||||||
|
| ((ref: React.RefObject<SuperFormRef<T>>) => Partial<FormSectionFooterProps>)
|
||||||
|
rightSection?: React.ReactNode
|
||||||
|
bodyRightSection?: BodyRightSection<T>
|
||||||
|
title?: string
|
||||||
|
showErrorList?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonFormProps<T extends FieldValues> {
|
||||||
|
gridRef?: React.MutableRefObject<GridRef<any> | null>
|
||||||
|
layoutProps?: SuperFormLayoutProps<T>
|
||||||
|
meta?: { [key: string]: any }
|
||||||
|
nested?: boolean
|
||||||
|
onCancel?: (request: RequestType) => void
|
||||||
|
onLayoutMounted?: () => void
|
||||||
|
onLayoutUnMounted?: () => void
|
||||||
|
onResetForm?: (data: T, form?: UseFormReturn<any, any, undefined>) => Promise<T>
|
||||||
|
onBeforeSubmit?: (
|
||||||
|
data: T,
|
||||||
|
request: RequestType,
|
||||||
|
form?: UseFormReturn<any, any, undefined>
|
||||||
|
) => Promise<T>
|
||||||
|
onSubmit?: (
|
||||||
|
data: T,
|
||||||
|
request: RequestType,
|
||||||
|
formData?: T,
|
||||||
|
form?: UseFormReturn<any, any, undefined>,
|
||||||
|
closeForm?: boolean
|
||||||
|
) => void
|
||||||
|
primeData?: any
|
||||||
|
readonly?: boolean
|
||||||
|
remote?: RemoteConfig
|
||||||
|
request: RequestType
|
||||||
|
persist?: boolean | { storageKey?: string }
|
||||||
|
useMutationOptions?: UseMutationOptions<any, Error, T, unknown>
|
||||||
|
useQueryOptions?: Partial<UseQueryOptions<any, Error, T>>
|
||||||
|
value?: T | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormProps<T extends FieldValues> extends CommonFormProps<T> {
|
||||||
|
children: React.ReactNode | ((props: UseFormReturn<T, any, undefined>) => React.ReactNode)
|
||||||
|
useFormProps?: UseFormProps<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormProviderProps extends Omit<SuperFormProps<any>, 'children'> {
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormModalProps<T extends FieldValues> extends SuperFormProps<T> {
|
||||||
|
modalProps: ModalProps
|
||||||
|
noCloseOnSubmit?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormPopoverProps<T extends FieldValues> extends SuperFormProps<T> {
|
||||||
|
popoverProps?: Omit<PopoverProps, 'children'>
|
||||||
|
target: any
|
||||||
|
noCloseOnSubmit?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedDrawerProps extends DrawerProps {
|
||||||
|
// Add any extended drawer props needed
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormDrawerProps<T extends FieldValues> extends SuperFormProps<T> {
|
||||||
|
drawerProps: ExtendedDrawerProps
|
||||||
|
noCloseOnSubmit?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormRef<T extends FieldValues> {
|
||||||
|
form: UseFormReturn<T, any, undefined>
|
||||||
|
mutation: Partial<UseMutationResult<any, Error, any, unknown>>
|
||||||
|
submit: (closeForm?: boolean, afterSubmit?: (data: T | any) => void) => Promise<void>
|
||||||
|
queryKey?: any
|
||||||
|
getFormState: () => UseFormStateReturn<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormDrawerRef<T extends FieldValues> extends SuperFormRef<T> {
|
||||||
|
drawer: HTMLDivElement | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormModalRef<T extends FieldValues> extends SuperFormRef<T> {
|
||||||
|
modal: HTMLDivElement | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperFormPopoverRef<T extends FieldValues> extends SuperFormRef<T> {
|
||||||
|
popover: HTMLDivElement | null
|
||||||
|
}
|
||||||
2
src/Form/types/index.ts
Normal file
2
src/Form/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './form.types'
|
||||||
|
export * from './remote.types'
|
||||||
11
src/Form/types/remote.types.ts
Normal file
11
src/Form/types/remote.types.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export interface RemoteConfig {
|
||||||
|
apiOptions?: RequestInit
|
||||||
|
apiURL?: string
|
||||||
|
enabled?: boolean
|
||||||
|
fetchSize?: number
|
||||||
|
hotFields?: string[]
|
||||||
|
primaryKey?: string
|
||||||
|
sqlFilter?: string
|
||||||
|
tableName: string
|
||||||
|
uniqueKeys?: string[]
|
||||||
|
}
|
||||||
161
src/Form/utils/fetchClient.ts
Normal file
161
src/Form/utils/fetchClient.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
export interface FetchOptions extends RequestInit {
|
||||||
|
params?: Record<string, any>
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchResponse<T = any> {
|
||||||
|
data: T
|
||||||
|
status: number
|
||||||
|
statusText: string
|
||||||
|
ok: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FetchError extends Error {
|
||||||
|
constructor(
|
||||||
|
public message: string,
|
||||||
|
public status?: number,
|
||||||
|
public response?: any
|
||||||
|
) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'FetchError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch wrapper with timeout support and axios-like interface
|
||||||
|
*/
|
||||||
|
async function fetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
options: FetchOptions = {}
|
||||||
|
): Promise<Response> {
|
||||||
|
const { timeout = 30000, ...fetchOptions } = options
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...fetchOptions,
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET request
|
||||||
|
*/
|
||||||
|
export async function get<T = any>(
|
||||||
|
url: string,
|
||||||
|
options?: FetchOptions
|
||||||
|
): Promise<FetchResponse<T>> {
|
||||||
|
try {
|
||||||
|
const response = await fetchWithTimeout(url, {
|
||||||
|
...options,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
ok: response.ok,
|
||||||
|
error: response.ok ? undefined : data?.message || data?.error || response.statusText,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new FetchError(
|
||||||
|
error instanceof Error ? error.message : 'Network request failed',
|
||||||
|
undefined,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST request
|
||||||
|
*/
|
||||||
|
export async function post<T = any>(
|
||||||
|
url: string,
|
||||||
|
data?: any,
|
||||||
|
options?: FetchOptions
|
||||||
|
): Promise<FetchResponse<T>> {
|
||||||
|
try {
|
||||||
|
const response = await fetchWithTimeout(url, {
|
||||||
|
...options,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
|
||||||
|
const responseData = await response.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
ok: response.ok,
|
||||||
|
error: response.ok ? undefined : responseData?.message || responseData?.error || response.statusText,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new FetchError(
|
||||||
|
error instanceof Error ? error.message : 'Network request failed',
|
||||||
|
undefined,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE request
|
||||||
|
*/
|
||||||
|
export async function del<T = any>(
|
||||||
|
url: string,
|
||||||
|
options?: FetchOptions
|
||||||
|
): Promise<FetchResponse<T>> {
|
||||||
|
try {
|
||||||
|
const response = await fetchWithTimeout(url, {
|
||||||
|
...options,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json().catch(() => ({}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
ok: response.ok,
|
||||||
|
error: response.ok ? undefined : data?.message || data?.error || response.statusText,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new FetchError(
|
||||||
|
error instanceof Error ? error.message : 'Network request failed',
|
||||||
|
undefined,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchClient = {
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
delete: del,
|
||||||
|
}
|
||||||
9
src/Form/utils/getNestedValue.ts
Normal file
9
src/Form/utils/getNestedValue.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Retrieves a nested value from an object using dot notation path
|
||||||
|
* @param path - Dot-separated path (e.g., "user.address.city")
|
||||||
|
* @param obj - Object to extract value from
|
||||||
|
* @returns The value at the specified path, or undefined if not found
|
||||||
|
*/
|
||||||
|
export const getNestedValue = (path: string, obj: any): any => {
|
||||||
|
return path.split('.').reduce((prev, curr) => prev?.[curr], obj)
|
||||||
|
}
|
||||||
30
src/Form/utils/openConfirmModal.ts
Normal file
30
src/Form/utils/openConfirmModal.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Stack, Text } from '@mantine/core'
|
||||||
|
import { modals } from '@mantine/modals'
|
||||||
|
|
||||||
|
export const openConfirmModal = (
|
||||||
|
onConfirm: () => void,
|
||||||
|
onCancel?: (() => void) | null,
|
||||||
|
description?: string | null
|
||||||
|
) =>
|
||||||
|
modals.openConfirmModal({
|
||||||
|
size: 'xs',
|
||||||
|
children: (
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Text size='xs' c={description ? 'blue' : 'red'} fw='bold'>
|
||||||
|
You have unsaved changes in this form.
|
||||||
|
</Text>
|
||||||
|
<Text size='xs'>
|
||||||
|
{description ??
|
||||||
|
'Closing now will discard any modifications you have made. Are you sure you want to continue?'}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
labels: { confirm: description ? 'Restore' : 'Confirm', cancel: 'Cancel' },
|
||||||
|
confirmProps: { color: description ? 'blue' : 'red', size: 'compact-xs' },
|
||||||
|
cancelProps: { size: 'compact-xs' },
|
||||||
|
groupProps: { gap: 'xs' },
|
||||||
|
withCloseButton: false,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
})
|
||||||
188
src/Former/Former.store.tsx
Normal file
188
src/Former/Former.store.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
|
||||||
|
import type { FormerProps, FormerState } from './Former.types';
|
||||||
|
|
||||||
|
const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||||
|
FormerState<any> & Partial<FormerProps<any>>,
|
||||||
|
FormerProps<any>
|
||||||
|
>(
|
||||||
|
(set, get) => ({
|
||||||
|
getState: (key) => {
|
||||||
|
const current = get();
|
||||||
|
return current?.[key];
|
||||||
|
},
|
||||||
|
load: async (reset?: boolean) => {
|
||||||
|
try {
|
||||||
|
set({ loading: true });
|
||||||
|
const keyName = get()?.apiKeyField || 'id';
|
||||||
|
const keyValue = (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
||||||
|
if (get().onAPICall && keyValue !== undefined) {
|
||||||
|
let data = await get().onAPICall!(
|
||||||
|
'read',
|
||||||
|
get().request || 'insert',
|
||||||
|
get().values,
|
||||||
|
keyValue
|
||||||
|
);
|
||||||
|
if (get().afterGet) {
|
||||||
|
data = await get().afterGet!({ ...data });
|
||||||
|
}
|
||||||
|
set({ loading: false, values: data });
|
||||||
|
get().onChange?.(data);
|
||||||
|
}
|
||||||
|
if (reset && get().getFormMethods) {
|
||||||
|
const formMethods = get().getFormMethods!();
|
||||||
|
formMethods.reset();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
set({ error: (e as Error)?.message ?? e, loading: false });
|
||||||
|
}
|
||||||
|
set({ loading: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange: (values) => {
|
||||||
|
set({ values });
|
||||||
|
},
|
||||||
|
request: 'insert',
|
||||||
|
reset: async () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.getFormMethods) {
|
||||||
|
if (state.request !== 'insert') {
|
||||||
|
await state.load(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formMethods = state.getFormMethods!();
|
||||||
|
formMethods.reset({ ...state.values, ...state.primeData });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save: async (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => {
|
||||||
|
try {
|
||||||
|
const keepOpen = get().keepOpen ?? false;
|
||||||
|
set({ loading: true });
|
||||||
|
if (get().getFormMethods) {
|
||||||
|
const formMethods = get().getFormMethods!();
|
||||||
|
|
||||||
|
let data = formMethods.getValues();
|
||||||
|
|
||||||
|
if (get().beforeSave) {
|
||||||
|
const newData = await get().beforeSave!(data);
|
||||||
|
data = newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let exit = false;
|
||||||
|
const handler = formMethods.handleSubmit(
|
||||||
|
(newdata) => {
|
||||||
|
data = newdata;
|
||||||
|
},
|
||||||
|
(errors) => {
|
||||||
|
set({ error: errors.root?.message || 'Validation errors', loading: false });
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await handler(e);
|
||||||
|
|
||||||
|
//console.log('Former.store.tsx save called', success, e, data, get().getFormMethods);
|
||||||
|
if (exit) {
|
||||||
|
set({ loading: false });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get().request === 'delete' && !get().deleteConfirmed) {
|
||||||
|
const confirmed = (await get().onConfirmDelete?.(data)) ?? false;
|
||||||
|
if (!confirmed) {
|
||||||
|
set({ loading: false });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get().onAPICall) {
|
||||||
|
const keyName = get()?.apiKeyField || 'id';
|
||||||
|
const keyValue =
|
||||||
|
(get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
||||||
|
const savedData = await get().onAPICall!(
|
||||||
|
'mutate',
|
||||||
|
get().request || 'insert',
|
||||||
|
data,
|
||||||
|
keyValue
|
||||||
|
);
|
||||||
|
if (get().afterSave) {
|
||||||
|
await get().afterSave!(savedData);
|
||||||
|
}
|
||||||
|
set({ loading: false, values: savedData });
|
||||||
|
get().onChange?.(savedData);
|
||||||
|
if (!keepOpen) {
|
||||||
|
get().onClose?.(savedData);
|
||||||
|
}
|
||||||
|
return savedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ loading: false, values: data });
|
||||||
|
get().onChange?.(data);
|
||||||
|
if (!keepOpen) {
|
||||||
|
get().onClose?.(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
set({ error: (e as Error)?.message ?? e, loading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
setRequest: (request) => {
|
||||||
|
set({ request });
|
||||||
|
},
|
||||||
|
setState: (key, value) => {
|
||||||
|
set(
|
||||||
|
produce((state) => {
|
||||||
|
state[key] = value;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
setStateFN: (key, value) => {
|
||||||
|
const p = new Promise<void>((resolve, reject) => {
|
||||||
|
set(
|
||||||
|
produce((state) => {
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
state[key] = (value as (value: unknown) => unknown)(state[key]);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Not a function ${value}`));
|
||||||
|
throw Error(`Not a function ${value}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
validate: async () => {
|
||||||
|
if (get().getFormMethods) {
|
||||||
|
const formMethods = get().getFormMethods!();
|
||||||
|
const isValid = await formMethods.trigger();
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
values: undefined,
|
||||||
|
}),
|
||||||
|
({ onConfirmDelete, primeData, request, values }) => {
|
||||||
|
let _onConfirmDelete = onConfirmDelete;
|
||||||
|
if (!onConfirmDelete) {
|
||||||
|
_onConfirmDelete = async () => {
|
||||||
|
return confirm('Are you sure you want to delete this item?');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
onConfirmDelete: _onConfirmDelete,
|
||||||
|
primeData,
|
||||||
|
request: request || 'insert',
|
||||||
|
values: { ...primeData, ...values },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export { FormerProvider };
|
||||||
|
export { useFormerStore };
|
||||||
115
src/Former/Former.tsx
Normal file
115
src/Former/Former.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { forwardRef, type PropsWithChildren, useEffect, useImperativeHandle } from 'react';
|
||||||
|
import { type FieldValues, FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
import type { FormerProps, FormerRef } from './Former.types';
|
||||||
|
|
||||||
|
import { FormerProvider, useFormerStore } from './Former.store';
|
||||||
|
import { FormerLayout } from './FormerLayout';
|
||||||
|
|
||||||
|
const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & PropsWithChildren>(
|
||||||
|
function FormerInner<T extends FieldValues>(
|
||||||
|
props: Partial<FormerProps<T>> & PropsWithChildren<T>,
|
||||||
|
ref: any
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
onChange,
|
||||||
|
onClose,
|
||||||
|
onOpen,
|
||||||
|
opened,
|
||||||
|
primeData,
|
||||||
|
reset,
|
||||||
|
save,
|
||||||
|
setState,
|
||||||
|
useFormProps,
|
||||||
|
validate,
|
||||||
|
values,
|
||||||
|
wrapper,
|
||||||
|
} = useFormerStore((state) => ({
|
||||||
|
getState: state.getState,
|
||||||
|
onChange: state.onChange,
|
||||||
|
onClose: state.onClose,
|
||||||
|
onOpen: state.onOpen,
|
||||||
|
opened: state.opened,
|
||||||
|
primeData: state.primeData,
|
||||||
|
reset: state.reset,
|
||||||
|
save: state.save,
|
||||||
|
setState: state.setState,
|
||||||
|
useFormProps: state.useFormProps,
|
||||||
|
validate: state.validate,
|
||||||
|
values: state.values,
|
||||||
|
wrapper: state.wrapper,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const formMethods = useForm<T>({
|
||||||
|
defaultValues: primeData,
|
||||||
|
mode: 'all',
|
||||||
|
shouldUseNativeValidation: true,
|
||||||
|
values: values,
|
||||||
|
...useFormProps,
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
close: async () => {
|
||||||
|
//console.log('close called');
|
||||||
|
onClose?.();
|
||||||
|
setState('opened', false);
|
||||||
|
},
|
||||||
|
getValue: () => {
|
||||||
|
return getState('values');
|
||||||
|
},
|
||||||
|
reset: () => {
|
||||||
|
reset();
|
||||||
|
},
|
||||||
|
save: async () => {
|
||||||
|
return await save();
|
||||||
|
},
|
||||||
|
setValue: (value: T) => {
|
||||||
|
onChange?.(value);
|
||||||
|
},
|
||||||
|
show: async () => {
|
||||||
|
//console.log('show called');
|
||||||
|
setState('opened', true);
|
||||||
|
onOpen?.();
|
||||||
|
},
|
||||||
|
validate: async () => {
|
||||||
|
return await validate();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[getState, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState('getFormMethods', () => formMethods);
|
||||||
|
}, [formMethods]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...formMethods}>
|
||||||
|
{typeof wrapper === 'function' ? (
|
||||||
|
wrapper(<FormerLayout>{props.children}</FormerLayout>, opened, onClose, onOpen, getState)
|
||||||
|
) : (
|
||||||
|
<FormerLayout>{props.children || null}</FormerLayout>
|
||||||
|
)}
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Former = forwardRef<FormerRef<any>, FormerProps<any> & PropsWithChildren>(
|
||||||
|
function Former<T extends FieldValues = any>(
|
||||||
|
props: FormerProps<T> & PropsWithChildren<T>,
|
||||||
|
ref: any
|
||||||
|
) {
|
||||||
|
//if opened is false and wrapper is defined as function, do not render anything
|
||||||
|
if (!props.opened && typeof props.wrapper === 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<FormerProvider {...props}>
|
||||||
|
<FormerInner ref={ref}>{props.children}</FormerInner>
|
||||||
|
</FormerProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
73
src/Former/Former.types.ts
Normal file
73
src/Former/Former.types.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type { LoadingOverlayProps, ScrollAreaAutosizeProps } from '@mantine/core';
|
||||||
|
import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form';
|
||||||
|
|
||||||
|
export interface FormerProps<T extends FieldValues = any> {
|
||||||
|
afterGet?: (data: T) => Promise<T> | void;
|
||||||
|
afterSave?: (data: T) => Promise<void> | void;
|
||||||
|
apiKeyField?: string;
|
||||||
|
beforeSave?: (data: T) => Promise<T> | T;
|
||||||
|
disableHTMlForm?: boolean;
|
||||||
|
keepOpen?: boolean;
|
||||||
|
onAPICall?: (
|
||||||
|
mode: 'mutate' | 'read',
|
||||||
|
request: RequestType,
|
||||||
|
value?: T,
|
||||||
|
key?: number | string
|
||||||
|
) => Promise<T>;
|
||||||
|
onCancel?: () => void;
|
||||||
|
onChange?: (value: T) => void;
|
||||||
|
onClose?: (data?: T) => void;
|
||||||
|
onConfirmDelete?: (values?: T) => Promise<boolean>;
|
||||||
|
onOpen?: (data?: T) => void;
|
||||||
|
|
||||||
|
opened?: boolean;
|
||||||
|
primeData?: T;
|
||||||
|
request: RequestType;
|
||||||
|
useFormProps?: UseFormProps<T>;
|
||||||
|
values?: T;
|
||||||
|
wrapper?: (
|
||||||
|
children: React.ReactNode,
|
||||||
|
opened: boolean | undefined,
|
||||||
|
onClose: ((data?: T) => void) | undefined,
|
||||||
|
onOpen: ((data?: T) => void) | undefined,
|
||||||
|
getState: <K extends keyof FormStateAndProps<T>>(key: K) => FormStateAndProps<T>[K]
|
||||||
|
) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormerRef<T extends FieldValues = any> {
|
||||||
|
close: () => Promise<void>;
|
||||||
|
getValue: () => T | undefined;
|
||||||
|
reset: () => void;
|
||||||
|
save: () => Promise<T | undefined>;
|
||||||
|
setValue: (value: T) => void;
|
||||||
|
show: () => Promise<void>;
|
||||||
|
validate: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormerState<T extends FieldValues = any> {
|
||||||
|
deleteConfirmed?: boolean;
|
||||||
|
error?: string;
|
||||||
|
getFormMethods?: () => UseFormReturn<any, any>;
|
||||||
|
getState: <K extends keyof FormStateAndProps<T>>(key: K) => FormStateAndProps<T>[K];
|
||||||
|
load: (reset?: boolean) => Promise<void>;
|
||||||
|
loading?: boolean;
|
||||||
|
loadingOverlayProps?: LoadingOverlayProps;
|
||||||
|
reset: (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => Promise<void>;
|
||||||
|
save: (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => Promise<T | undefined>;
|
||||||
|
scrollAreaProps?: ScrollAreaAutosizeProps;
|
||||||
|
setRequest: (request: RequestType) => void;
|
||||||
|
setState: <K extends keyof FormStateAndProps<T>>(
|
||||||
|
key: K,
|
||||||
|
value: Partial<FormStateAndProps<T>>[K]
|
||||||
|
) => void;
|
||||||
|
setStateFN: <K extends keyof FormStateAndProps<T>>(
|
||||||
|
key: K,
|
||||||
|
value: (current: FormStateAndProps<T>[K]) => Partial<FormStateAndProps<T>[K]>
|
||||||
|
) => Promise<void>;
|
||||||
|
validate: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormStateAndProps<T extends FieldValues = any> = FormerProps<T> &
|
||||||
|
Partial<FormerState<T>>;
|
||||||
|
|
||||||
|
export type RequestType = 'delete' | 'insert' | 'select' | 'update' | 'view';
|
||||||
93
src/Former/FormerLayout.tsx
Normal file
93
src/Former/FormerLayout.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { LoadingOverlay, ScrollAreaAutosize } from '@mantine/core';
|
||||||
|
import { type PropsWithChildren, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useFormerStore } from './Former.store';
|
||||||
|
|
||||||
|
export const FormerLayout = (props: PropsWithChildren) => {
|
||||||
|
const {
|
||||||
|
disableHTMlForm,
|
||||||
|
getFormMethods,
|
||||||
|
load,
|
||||||
|
loading,
|
||||||
|
loadingOverlayProps,
|
||||||
|
request,
|
||||||
|
reset,
|
||||||
|
save,
|
||||||
|
scrollAreaProps,
|
||||||
|
} = useFormerStore((state) => ({
|
||||||
|
disableHTMlForm: state.disableHTMlForm,
|
||||||
|
getFormMethods: state.getFormMethods,
|
||||||
|
load: state.load,
|
||||||
|
loading: state.loading,
|
||||||
|
loadingOverlayProps: state.loadingOverlayProps,
|
||||||
|
request: state.request,
|
||||||
|
reset: state.reset,
|
||||||
|
save: state.save,
|
||||||
|
scrollAreaProps: state.scrollAreaProps,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getFormMethods) {
|
||||||
|
const formMethods = getFormMethods();
|
||||||
|
if (formMethods && request !== 'insert') {
|
||||||
|
load(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [getFormMethods, request]);
|
||||||
|
|
||||||
|
if (disableHTMlForm) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaAutosize
|
||||||
|
offsetScrollbars
|
||||||
|
scrollbarSize={4}
|
||||||
|
type="auto"
|
||||||
|
{...scrollAreaProps}
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '89vh',
|
||||||
|
padding: '0.25rem',
|
||||||
|
width: '100%',
|
||||||
|
...scrollAreaProps?.style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
<LoadingOverlay
|
||||||
|
loaderProps={{ type: 'bars' }}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
}}
|
||||||
|
{...loadingOverlayProps}
|
||||||
|
visible={loading}
|
||||||
|
/>
|
||||||
|
</ScrollAreaAutosize>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollAreaAutosize
|
||||||
|
offsetScrollbars
|
||||||
|
scrollbarSize={4}
|
||||||
|
type="auto"
|
||||||
|
{...scrollAreaProps}
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '89vh',
|
||||||
|
padding: '0.25rem',
|
||||||
|
width: '100%',
|
||||||
|
...scrollAreaProps?.style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form onReset={(e) => reset(e)} onSubmit={(e) => save(e)}>
|
||||||
|
{props.children}
|
||||||
|
<LoadingOverlay
|
||||||
|
loaderProps={{ type: 'bars' }}
|
||||||
|
overlayProps={{
|
||||||
|
backgroundOpacity: 0.5,
|
||||||
|
}}
|
||||||
|
{...loadingOverlayProps}
|
||||||
|
visible={loading}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</ScrollAreaAutosize>
|
||||||
|
);
|
||||||
|
};
|
||||||
0
src/Former/index.ts
Normal file
0
src/Former/index.ts
Normal file
42
src/Former/stories/Gridler.goapi.stories.tsx
Normal file
42
src/Former/stories/Gridler.goapi.stories.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import { Box } from '@mantine/core';
|
||||||
|
import { fn } from 'storybook/test';
|
||||||
|
|
||||||
|
import { FormTest } from './example';
|
||||||
|
|
||||||
|
const Renderable = (props: any) => {
|
||||||
|
return (
|
||||||
|
<Box h="100%" mih="400px" miw="400px" w="100%">
|
||||||
|
<FormTest {...props} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||||
|
args: { onClick: fn() },
|
||||||
|
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||||
|
argTypes: {
|
||||||
|
backgroundColor: { control: 'color' },
|
||||||
|
},
|
||||||
|
component: Renderable,
|
||||||
|
parameters: {
|
||||||
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||||
|
//layout: 'centered',
|
||||||
|
},
|
||||||
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
|
tags: ['autodocs'],
|
||||||
|
title: 'Former/Former Basic',
|
||||||
|
} satisfies Meta<typeof Renderable>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||||
|
export const BasicExample: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Test',
|
||||||
|
},
|
||||||
|
};
|
||||||
118
src/Former/stories/example.tsx
Normal file
118
src/Former/stories/example.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { Button, Drawer, Group, Paper, Select, Stack, Switch } from '@mantine/core';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { Controller } from 'react-hook-form';
|
||||||
|
|
||||||
|
import type { FormerRef } from '../Former.types';
|
||||||
|
|
||||||
|
import { Former } from '../Former';
|
||||||
|
|
||||||
|
export const FormTest = () => {
|
||||||
|
const [request, setRequest] = useState<null | string>('insert');
|
||||||
|
const [wrapped, setWrapped] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({ a: 99 });
|
||||||
|
console.log('formData', formData);
|
||||||
|
|
||||||
|
const ref = useRef<FormerRef>(null);
|
||||||
|
return (
|
||||||
|
<Stack h="100%" mih="400px" w="90%">
|
||||||
|
<Select
|
||||||
|
data={['insert', 'update', 'delete', 'select', 'view']}
|
||||||
|
onChange={setRequest}
|
||||||
|
value={request}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
checked={wrapped}
|
||||||
|
label="Wrapped in Drawer"
|
||||||
|
onChange={(event) => setWrapped(event.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => setOpen(true)}>Open Former Drawer</Button>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
const valid = await ref.current?.validate();
|
||||||
|
console.log('validate -> ', valid, ref.current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Test Ref Values. See console
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
ref.current?.close?.();
|
||||||
|
}, 3000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Test Show/Hide
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<Former
|
||||||
|
//wrapper={(children, getState) => <div>{children}</div>}
|
||||||
|
//opened={true}
|
||||||
|
apiKeyField="a"
|
||||||
|
onAPICall={(mode, request, value) => {
|
||||||
|
console.log('API Call', mode, request, value);
|
||||||
|
if (mode === 'read') {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ a: 'Another Value', test: 'Loaded Value' });
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(value || {});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onChange={setFormData}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
opened={open}
|
||||||
|
primeData={{ a: '66', test: 'primed' }}
|
||||||
|
ref={ref}
|
||||||
|
request={request as any}
|
||||||
|
useFormProps={{ criteriaMode: 'all', shouldUseNativeValidation: false }}
|
||||||
|
values={formData}
|
||||||
|
wrapper={
|
||||||
|
wrapped
|
||||||
|
? (children, opened, onClose, onOpen, getState) => {
|
||||||
|
const values = getState('values');
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
h={'100%'}
|
||||||
|
onClose={() => onClose?.()}
|
||||||
|
opened={opened ?? false}
|
||||||
|
title={`Drawer Former - Current A Value: ${values?.a}`}
|
||||||
|
w={'50%'}
|
||||||
|
>
|
||||||
|
<Paper h="100%" shadow="sm" w="100%" withBorder>
|
||||||
|
{children}
|
||||||
|
<Button>Test Save</Button>
|
||||||
|
</Paper>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack h="1200px">
|
||||||
|
<Stack>
|
||||||
|
<Controller
|
||||||
|
name="test"
|
||||||
|
render={({ field }) => <input type="text" {...field} placeholder="A" />}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="a"
|
||||||
|
render={({ field }) => <input type="text" {...field} placeholder="B" />}
|
||||||
|
rules={{ required: 'Field is required' }}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
<button type="reset">Reset</button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Former>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
11
src/Former/todo.md
Normal file
11
src/Former/todo.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- [ ] Headerspec API
|
||||||
|
- [ ] Relspec API
|
||||||
|
- [ ] SocketSpec API
|
||||||
|
- [ ] Layout Tool
|
||||||
|
- [ ] Header Section
|
||||||
|
- [ ] Button Section
|
||||||
|
- [ ] Footer Section
|
||||||
|
- [ ] Different Loaded for saving vs loading
|
||||||
|
- [ ] Better Confirm Dialog
|
||||||
|
- [ ] Reset Confirm Dialog
|
||||||
|
- [ ] Request insert and save but keep open (must clear key from API, also add callback)
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import '@glideapps/glide-data-grid/dist/index.css';
|
import '@glideapps/glide-data-grid/dist/index.css';
|
||||||
|
import React, { type Ref } from 'react';
|
||||||
|
|
||||||
import { MantineBetterMenusProvider } from '../MantineBetterMenu';
|
import { MantineBetterMenusProvider } from '../MantineBetterMenu';
|
||||||
import { GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor';
|
import { GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor';
|
||||||
import { GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor';
|
import { GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor';
|
||||||
import { type GridlerProps, Provider } from './components/GridlerStore';
|
import { type GridlerProps, type GridlerRef, Provider } from './components/GridlerStore';
|
||||||
|
import { GridlerRefHandler } from './components/RefHandler';
|
||||||
import { GridlerDataGrid } from './GridlerDataGrid';
|
import { GridlerDataGrid } from './GridlerDataGrid';
|
||||||
|
|
||||||
const Gridler = (props: GridlerProps) => {
|
const _Gridler = (props: GridlerProps, ref: Ref<GridlerRef> | undefined) => {
|
||||||
return (
|
return (
|
||||||
<MantineBetterMenusProvider>
|
<MantineBetterMenusProvider>
|
||||||
<Provider
|
<Provider
|
||||||
@@ -19,12 +21,20 @@ const Gridler = (props: GridlerProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GridlerDataGrid />
|
<GridlerDataGrid />
|
||||||
|
<GridlerRefHandler ref={ref} />
|
||||||
{props.children}
|
{props.children}
|
||||||
</Provider>
|
</Provider>
|
||||||
</MantineBetterMenusProvider>
|
</MantineBetterMenusProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GridlerComponentType = {
|
||||||
|
FormAdaptor: typeof GlidlerFormAdaptor;
|
||||||
|
LocalDataAdaptor: typeof GlidlerLocalDataAdaptor;
|
||||||
|
} & React.ForwardRefExoticComponent<GridlerProps & React.RefAttributes<GridlerRef>>;
|
||||||
|
|
||||||
|
const Gridler = React.forwardRef(_Gridler) as GridlerComponentType;
|
||||||
|
|
||||||
Gridler.FormAdaptor = GlidlerFormAdaptor;
|
Gridler.FormAdaptor = GlidlerFormAdaptor;
|
||||||
Gridler.LocalDataAdaptor = GlidlerLocalDataAdaptor;
|
Gridler.LocalDataAdaptor = GlidlerLocalDataAdaptor;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { BottomBar } from './components/BottomBar';
|
|||||||
import { Computer } from './components/Computer';
|
import { Computer } from './components/Computer';
|
||||||
import { useGridlerStore } from './components/GridlerStore';
|
import { useGridlerStore } from './components/GridlerStore';
|
||||||
import { Pager } from './components/Pager';
|
import { Pager } from './components/Pager';
|
||||||
import { RightMenuIcon } from './components/RightMenuIcon';
|
import { GridlerRightMenuIcon } from './components/RightMenuIcon';
|
||||||
import { SortSprite } from './components/sprites/Sort';
|
import { SortSprite } from './components/sprites/Sort';
|
||||||
import { SortDownSprite } from './components/sprites/SortDown';
|
import { SortDownSprite } from './components/sprites/SortDown';
|
||||||
import { SortUpSprite } from './components/sprites/SortUp';
|
import { SortUpSprite } from './components/sprites/SortUp';
|
||||||
@@ -40,6 +40,7 @@ export const GridlerDataGrid = () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
_gridSelection,
|
_gridSelection,
|
||||||
|
allowMultiSelect,
|
||||||
focused,
|
focused,
|
||||||
getCellContent,
|
getCellContent,
|
||||||
getCellsForSelection,
|
getCellsForSelection,
|
||||||
@@ -49,6 +50,7 @@ export const GridlerDataGrid = () => {
|
|||||||
headerHeight,
|
headerHeight,
|
||||||
heightProp,
|
heightProp,
|
||||||
mounted,
|
mounted,
|
||||||
|
onCellActivated,
|
||||||
onCellClicked,
|
onCellClicked,
|
||||||
onCellEdited,
|
onCellEdited,
|
||||||
onColumnMoved,
|
onColumnMoved,
|
||||||
@@ -69,6 +71,7 @@ export const GridlerDataGrid = () => {
|
|||||||
widthProp,
|
widthProp,
|
||||||
} = useGridlerStore((s) => ({
|
} = useGridlerStore((s) => ({
|
||||||
_gridSelection: s._gridSelection,
|
_gridSelection: s._gridSelection,
|
||||||
|
allowMultiSelect: s.allowMultiSelect,
|
||||||
focused: s.focused,
|
focused: s.focused,
|
||||||
getCellContent: s.getCellContent,
|
getCellContent: s.getCellContent,
|
||||||
getCellsForSelection: s.getCellsForSelection,
|
getCellsForSelection: s.getCellsForSelection,
|
||||||
@@ -78,6 +81,7 @@ export const GridlerDataGrid = () => {
|
|||||||
headerHeight: s.headerHeight,
|
headerHeight: s.headerHeight,
|
||||||
heightProp: s.height,
|
heightProp: s.height,
|
||||||
mounted: s.mounted,
|
mounted: s.mounted,
|
||||||
|
onCellActivated: s.onCellActivated,
|
||||||
onCellClicked: s.onCellClicked,
|
onCellClicked: s.onCellClicked,
|
||||||
onCellEdited: s.onCellEdited,
|
onCellEdited: s.onCellEdited,
|
||||||
onColumnMoved: s.onColumnMoved,
|
onColumnMoved: s.onColumnMoved,
|
||||||
@@ -157,16 +161,16 @@ export const GridlerDataGrid = () => {
|
|||||||
height={height ?? 400}
|
height={height ?? 400}
|
||||||
overscrollX={16}
|
overscrollX={16}
|
||||||
overscrollY={32}
|
overscrollY={32}
|
||||||
rangeSelect="multi-rect"
|
rangeSelect={allowMultiSelect ? 'multi-rect' : 'cell'}
|
||||||
rightElementProps={{
|
rightElementProps={{
|
||||||
fill: false,
|
fill: false,
|
||||||
sticky: true,
|
sticky: true,
|
||||||
}}
|
}}
|
||||||
rowMarkers={{
|
rowMarkers={{
|
||||||
checkboxStyle: 'square',
|
checkboxStyle: 'square',
|
||||||
kind: 'both',
|
kind: allowMultiSelect ? 'both' : 'clickable-number',
|
||||||
}}
|
}}
|
||||||
rowSelect="multi"
|
rowSelect={allowMultiSelect ? 'multi' : 'single'}
|
||||||
rowSelectionMode="auto"
|
rowSelectionMode="auto"
|
||||||
spanRangeBehavior="default"
|
spanRangeBehavior="default"
|
||||||
{...glideProps}
|
{...glideProps}
|
||||||
@@ -176,6 +180,7 @@ export const GridlerDataGrid = () => {
|
|||||||
gridSelection={_gridSelection}
|
gridSelection={_gridSelection}
|
||||||
headerHeight={headerHeight ?? 32}
|
headerHeight={headerHeight ?? 32}
|
||||||
headerIcons={{ sort: SortSprite, sortdown: SortDownSprite, sortup: SortUpSprite }}
|
headerIcons={{ sort: SortSprite, sortdown: SortDownSprite, sortup: SortUpSprite }}
|
||||||
|
onCellActivated={onCellActivated}
|
||||||
onCellClicked={onCellClicked}
|
onCellClicked={onCellClicked}
|
||||||
onCellContextMenu={(cell, event) => {
|
onCellContextMenu={(cell, event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -195,7 +200,13 @@ export const GridlerDataGrid = () => {
|
|||||||
onGridSelectionChange={(selection) => {
|
onGridSelectionChange={(selection) => {
|
||||||
let rows = CompactSelection.empty();
|
let rows = CompactSelection.empty();
|
||||||
const currentSelection = getState('_gridSelection');
|
const currentSelection = getState('_gridSelection');
|
||||||
|
const keyField = getState('keyField') ?? 'id';
|
||||||
|
const getRowBuffer = getState('getRowBuffer');
|
||||||
for (const r of selection.rows) {
|
for (const r of selection.rows) {
|
||||||
|
const validRowID = getRowBuffer ? getRowBuffer(r)?.[keyField] : null;
|
||||||
|
if (!validRowID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
rows = rows.hasIndex(r) ? rows : rows.add(r);
|
rows = rows.hasIndex(r) ? rows : rows.add(r);
|
||||||
}
|
}
|
||||||
if (selectMode === 'row' && selection.current?.range) {
|
if (selectMode === 'row' && selection.current?.range) {
|
||||||
@@ -204,19 +215,32 @@ export const GridlerDataGrid = () => {
|
|||||||
y < selection.current.range.y + selection.current.range.height;
|
y < selection.current.range.y + selection.current.range.height;
|
||||||
y++
|
y++
|
||||||
) {
|
) {
|
||||||
|
const validRowID = getRowBuffer ? getRowBuffer(y)?.[keyField] : null;
|
||||||
|
if (!validRowID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
rows = rows.hasIndex(y) ? rows : rows.add(y);
|
rows = rows.hasIndex(y) ? rows : rows.add(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (rows.length === 0) {
|
||||||
|
for (const r of currentSelection?.rows ?? []) {
|
||||||
|
const validRowID = getRowBuffer ? getRowBuffer(r)?.[keyField] : null;
|
||||||
|
if (!validRowID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rows = rows.hasIndex(r) ? rows : rows.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Debug:onGridSelectionChange', currentSelection, selection);
|
||||||
if (
|
if (
|
||||||
JSON.stringify(currentSelection?.columns) !== JSON.stringify(selection.columns) ||
|
JSON.stringify(currentSelection?.columns) !== JSON.stringify(selection.columns) ||
|
||||||
JSON.stringify(currentSelection?.rows) !== JSON.stringify(rows) ||
|
JSON.stringify(currentSelection?.rows) !== JSON.stringify(rows) ||
|
||||||
JSON.stringify(currentSelection?.current) !== JSON.stringify(selection.current)
|
JSON.stringify(currentSelection?.current) !== JSON.stringify(selection.current)
|
||||||
) {
|
) {
|
||||||
setState('_gridSelection', { ...selection, rows });
|
setState('_gridSelection', { ...selection, rows });
|
||||||
if (JSON.stringify(currentSelection?.rows) !== JSON.stringify(rows)) {
|
//if (JSON.stringify(currentSelection?.rows) !== JSON.stringify(rows)) {
|
||||||
setState('_gridSelectionRows', rows);
|
setState('_gridSelectionRows', rows);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('Selection', selection);
|
//console.log('Selection', selection);
|
||||||
@@ -237,11 +261,13 @@ export const GridlerDataGrid = () => {
|
|||||||
onVisibleRegionChanged={onVisibleRegionChanged}
|
onVisibleRegionChanged={onVisibleRegionChanged}
|
||||||
ref={refMerged as React.Ref<DataEditorRef>}
|
ref={refMerged as React.Ref<DataEditorRef>}
|
||||||
rightElement={
|
rightElement={
|
||||||
<Group>
|
sections?.rightElementDisabled ? undefined : (
|
||||||
{sections?.rightElementStart}
|
<Group>
|
||||||
<RightMenuIcon />
|
{sections?.rightElementStart}
|
||||||
{sections?.rightElementEnd}
|
<GridlerRightMenuIcon />
|
||||||
</Group>
|
{sections?.rightElementEnd}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
rowHeight={rowHeight ?? 22}
|
rowHeight={rowHeight ?? 22}
|
||||||
//rowMarkersCheckboxStyle='square'
|
//rowMarkersCheckboxStyle='square'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable react/react-in-jsx-scope */
|
|
||||||
import { useGridlerStore } from './GridlerStore';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
export function BottomBar() {
|
export function BottomBar() {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface GridlerColumn extends Partial<BaseGridColumn> {
|
|||||||
disableFilter?: boolean;
|
disableFilter?: boolean;
|
||||||
disableMove?: boolean;
|
disableMove?: boolean;
|
||||||
disableResize?: boolean;
|
disableResize?: boolean;
|
||||||
|
disableSearch?: boolean;
|
||||||
disableSort?: boolean;
|
disableSort?: boolean;
|
||||||
getMenuItems?: (
|
getMenuItems?: (
|
||||||
id: string,
|
id: string,
|
||||||
@@ -35,6 +36,7 @@ export interface GridlerColumn extends Partial<BaseGridColumn> {
|
|||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
tooltip?: ((buffer: any, row: number, col: number) => ReactNode) | string;
|
tooltip?: ((buffer: any, row: number, col: number) => ReactNode) | string;
|
||||||
|
virtual?: boolean;
|
||||||
width?: number;
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CompactSelection } from '@glideapps/glide-data-grid';
|
import { CompactSelection } from '@glideapps/glide-data-grid';
|
||||||
|
import { useDebouncedCallback } from '@mantine/hooks';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { useGridlerStore } from './GridlerStore';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
@@ -6,42 +7,67 @@ import { useGridlerStore } from './GridlerStore';
|
|||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const Computer = React.memo(() => {
|
export const Computer = React.memo(() => {
|
||||||
const refFirstRun = useRef(0);
|
const refFirstRun = useRef(0);
|
||||||
|
const refLastSearch = useRef('');
|
||||||
const refLastFilters = useRef<unknown>(null);
|
const refLastFilters = useRef<unknown>(null);
|
||||||
const {
|
const {
|
||||||
_glideref,
|
_glideref,
|
||||||
_gridSelectionRows,
|
_gridSelectionRows,
|
||||||
askAPIRowNumber,
|
|
||||||
colFilters,
|
colFilters,
|
||||||
colOrder,
|
colOrder,
|
||||||
colSize,
|
colSize,
|
||||||
colSort,
|
colSort,
|
||||||
columns,
|
columns,
|
||||||
|
getRowIndexByKey,
|
||||||
getState,
|
getState,
|
||||||
loadPage,
|
loadPage,
|
||||||
ready,
|
ready,
|
||||||
|
|
||||||
|
scrollToRowKey,
|
||||||
|
searchStr,
|
||||||
|
selectedRowKey,
|
||||||
setState,
|
setState,
|
||||||
setStateFN,
|
setStateFN,
|
||||||
values,
|
values,
|
||||||
} = useGridlerStore((s) => ({
|
} = useGridlerStore((s) => ({
|
||||||
_glideref: s._glideref,
|
_glideref: s._glideref,
|
||||||
_gridSelectionRows: s._gridSelectionRows,
|
_gridSelectionRows: s._gridSelectionRows,
|
||||||
askAPIRowNumber: s.askAPIRowNumber,
|
|
||||||
colFilters: s.colFilters,
|
colFilters: s.colFilters,
|
||||||
colOrder: s.colOrder,
|
colOrder: s.colOrder,
|
||||||
colSize: s.colSize,
|
colSize: s.colSize,
|
||||||
colSort: s.colSort,
|
colSort: s.colSort,
|
||||||
columns: s.columns,
|
columns: s.columns,
|
||||||
|
getRowIndexByKey: s.getRowIndexByKey,
|
||||||
getState: s.getState,
|
getState: s.getState,
|
||||||
loadPage: s.loadPage,
|
loadPage: s.loadPage,
|
||||||
ready: s.ready,
|
ready: s.ready,
|
||||||
|
|
||||||
|
scrollToRowKey: s.scrollToRowKey,
|
||||||
|
searchStr: s.searchStr,
|
||||||
|
selectedRowKey: s.selectedRowKey,
|
||||||
setState: s.setState,
|
setState: s.setState,
|
||||||
setStateFN: s.setStateFN,
|
setStateFN: s.setStateFN,
|
||||||
uniqueid: s.uniqueid,
|
uniqueid: s.uniqueid,
|
||||||
values: s.values,
|
values: s.values,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const debouncedDoSearch = useDebouncedCallback(
|
||||||
|
(searchStr: string) => {
|
||||||
|
loadPage(0, 'all').then(() => {
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
getState('_events')?.dispatchEvent?.(
|
||||||
|
new CustomEvent('onSearched', {
|
||||||
|
detail: { search: searchStr },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 300,
|
||||||
|
leading: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//When values change, update selection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const searchSelection = async () => {
|
const searchSelection = async () => {
|
||||||
const page_data = getState('_page_data');
|
const page_data = getState('_page_data');
|
||||||
@@ -72,8 +98,8 @@ export const Computer = React.memo(() => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(rowIndex >= 0) && typeof askAPIRowNumber === 'function') {
|
if (!(rowIndex >= 0)) {
|
||||||
const idx = await askAPIRowNumber(key);
|
const idx = await getRowIndexByKey(key);
|
||||||
if (idx) {
|
if (idx) {
|
||||||
rowIndexes.push(idx);
|
rowIndexes.push(idx);
|
||||||
}
|
}
|
||||||
@@ -103,40 +129,12 @@ export const Computer = React.memo(() => {
|
|||||||
}
|
}
|
||||||
}, [values]);
|
}, [values]);
|
||||||
|
|
||||||
|
//Fire onChange when selection changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onChange = getState('onChange');
|
const onChange = getState('onChange');
|
||||||
if (onChange && typeof onChange === 'function') {
|
if (onChange && typeof onChange === 'function') {
|
||||||
const page_data = getState('_page_data');
|
const getGridSelectedRows = getState('getGridSelectedRows');
|
||||||
const pageSize = getState('pageSize');
|
const buffers = getGridSelectedRows();
|
||||||
|
|
||||||
const buffers = [];
|
|
||||||
if (_gridSelectionRows) {
|
|
||||||
for (const range of _gridSelectionRows) {
|
|
||||||
let buffer = undefined;
|
|
||||||
|
|
||||||
for (const p in page_data) {
|
|
||||||
for (const r in page_data[p]) {
|
|
||||||
const idx = Number(p) * pageSize + Number(r);
|
|
||||||
if (isNaN(idx)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number(page_data[p][r]?._rownumber) === range + 1) {
|
|
||||||
buffer = page_data[p][r];
|
|
||||||
//console.log('Found row', range, idx, page_data[p][r]?._rownumber);
|
|
||||||
break;
|
|
||||||
} else if (idx === range + 1) {
|
|
||||||
buffer = page_data[p][r];
|
|
||||||
//console.log('Found row 2', range, idx, page_data[p][r]?._rownumber);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (buffer !== undefined) {
|
|
||||||
buffers.push(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _values = getState('values');
|
const _values = getState('values');
|
||||||
|
|
||||||
@@ -144,7 +142,7 @@ export const Computer = React.memo(() => {
|
|||||||
onChange(buffers);
|
onChange(buffers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [JSON.stringify(_gridSelectionRows), getState]);
|
}, [_gridSelectionRows, _gridSelectionRows?.length, getState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState(
|
setState(
|
||||||
@@ -157,6 +155,17 @@ export const Computer = React.memo(() => {
|
|||||||
);
|
);
|
||||||
}, [columns]);
|
}, [columns]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchStr === undefined || searchStr === null) {
|
||||||
|
refLastSearch.current = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (refLastSearch.current !== searchStr) {
|
||||||
|
debouncedDoSearch(searchStr);
|
||||||
|
refLastSearch.current = searchStr;
|
||||||
|
}
|
||||||
|
}, [searchStr]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!colSort) {
|
if (!colSort) {
|
||||||
return;
|
return;
|
||||||
@@ -180,12 +189,14 @@ export const Computer = React.memo(() => {
|
|||||||
: (c.defaultIcon ?? 'sort'),
|
: (c.defaultIcon ?? 'sort'),
|
||||||
}));
|
}));
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
loadPage(0, 'all');
|
loadPage(0, 'all').then(() => {
|
||||||
getState('_events')?.dispatchEvent?.(
|
getState('refreshCells')?.();
|
||||||
new CustomEvent('onColumnSorted', {
|
getState('_events')?.dispatchEvent?.(
|
||||||
detail: { cols: colSort },
|
new CustomEvent('onColumnSorted', {
|
||||||
})
|
detail: { cols: colSort },
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, [colSort]);
|
}, [colSort]);
|
||||||
|
|
||||||
@@ -195,13 +206,15 @@ export const Computer = React.memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (JSON.stringify(refLastFilters.current) !== JSON.stringify(colFilters)) {
|
if (JSON.stringify(refLastFilters.current) !== JSON.stringify(colFilters)) {
|
||||||
loadPage(0, 'all');
|
loadPage(0, 'all').then(() => {
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
getState('_events')?.dispatchEvent?.(
|
||||||
|
new CustomEvent('onColumnFiltered', {
|
||||||
|
detail: { filters: colFilters },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
refLastFilters.current = colFilters;
|
refLastFilters.current = colFilters;
|
||||||
getState('_events')?.dispatchEvent?.(
|
|
||||||
new CustomEvent('onColumnFiltered', {
|
|
||||||
detail: { filters: colFilters },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [colFilters]);
|
}, [colFilters]);
|
||||||
|
|
||||||
@@ -214,6 +227,8 @@ export const Computer = React.memo(() => {
|
|||||||
...c,
|
...c,
|
||||||
width: c.id && colSize?.[c.id] ? colSize?.[c.id] : c.width,
|
width: c.id && colSize?.[c.id] ? colSize?.[c.id] : c.width,
|
||||||
}));
|
}));
|
||||||
|
}).then(() => {
|
||||||
|
getState('refreshCells')?.();
|
||||||
});
|
});
|
||||||
}, [colSize]);
|
}, [colSize]);
|
||||||
|
|
||||||
@@ -231,9 +246,12 @@ export const Computer = React.memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
}).then(() => {
|
||||||
|
getState('refreshCells')?.();
|
||||||
});
|
});
|
||||||
}, [colOrder]);
|
}, [colOrder]);
|
||||||
|
|
||||||
|
//Initial Load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!_glideref) {
|
if (!_glideref) {
|
||||||
return;
|
return;
|
||||||
@@ -242,9 +260,115 @@ export const Computer = React.memo(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refFirstRun.current = 1;
|
refFirstRun.current = 1;
|
||||||
loadPage(0);
|
loadPage(0).then(() => {
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
});
|
||||||
}, [ready, loadPage]);
|
}, [ready, loadPage]);
|
||||||
|
|
||||||
|
//Logic to select first row on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const _events = getState('_events');
|
||||||
|
const loadPage = () => {
|
||||||
|
const selectFirstRowOnMount = getState('selectFirstRowOnMount');
|
||||||
|
if (ready && selectFirstRowOnMount) {
|
||||||
|
const scrollToRowKey = getState('scrollToRowKey');
|
||||||
|
if (scrollToRowKey && scrollToRowKey >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keyField = getState('keyField') ?? 'id';
|
||||||
|
const page_data = getState('_page_data');
|
||||||
|
|
||||||
|
const firstBuffer = page_data?.[0]?.[0];
|
||||||
|
const firstRow = firstBuffer?.[keyField];
|
||||||
|
const currentValues = getState('values') ?? [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(values && values.length > 0) &&
|
||||||
|
firstRow &&
|
||||||
|
firstRow > 0 &&
|
||||||
|
(currentValues.length ?? 0) === 0
|
||||||
|
) {
|
||||||
|
const values = [firstBuffer, ...(currentValues as Array<Record<string, unknown>>)];
|
||||||
|
|
||||||
|
const onChange = getState('onChange');
|
||||||
|
//console.log('Selecting first row:', firstRow, firstBuffer, values);
|
||||||
|
if (onChange) {
|
||||||
|
onChange(values);
|
||||||
|
} else {
|
||||||
|
setState('values', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState('scrollToRowKey', firstRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_events?.addEventListener('loadPage', loadPage);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
_events?.removeEventListener('loadPage', loadPage);
|
||||||
|
};
|
||||||
|
}, [ready]);
|
||||||
|
|
||||||
|
/// logic to apply the selected row.
|
||||||
|
// useEffect(() => {
|
||||||
|
// const ready = getState('ready');
|
||||||
|
// const ref = getState('_glideref');
|
||||||
|
// const getRowIndexByKey = getState('getRowIndexByKey');
|
||||||
|
|
||||||
|
// if (scrollToRowKey && ref && ready) {
|
||||||
|
// getRowIndexByKey?.(scrollToRowKey).then((r) => {
|
||||||
|
// if (r !== undefined) {
|
||||||
|
// //console.log('Scrolling to selected row:', scrollToRowKey, r);
|
||||||
|
// ref.scrollTo(0, r);
|
||||||
|
// getState('_events').dispatchEvent(
|
||||||
|
// new CustomEvent('scrollToRowKeyFound', {
|
||||||
|
// detail: { rowNumber: r, scrollToRowKey: scrollToRowKey },
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }, [scrollToRowKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ready = getState('ready');
|
||||||
|
const ref = getState('_glideref');
|
||||||
|
const getRowIndexByKey = getState('getRowIndexByKey');
|
||||||
|
const key = selectedRowKey ?? scrollToRowKey;
|
||||||
|
|
||||||
|
if (key && ref && ready) {
|
||||||
|
getRowIndexByKey?.(key).then((r) => {
|
||||||
|
if (r !== undefined) {
|
||||||
|
//console.log('Scrolling to selected row:', r, selectedRowKey, scrollToRowKey);
|
||||||
|
|
||||||
|
if (selectedRowKey) {
|
||||||
|
const onChange = getState('onChange');
|
||||||
|
const selected = [{ [getState('keyField') ?? 'id']: selectedRowKey }];
|
||||||
|
if (JSON.stringify(getState('values')) !== JSON.stringify(selected)) {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(selected);
|
||||||
|
} else {
|
||||||
|
setState('values', selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.scrollTo(0, r);
|
||||||
|
getState('_events').dispatchEvent(
|
||||||
|
new CustomEvent('scrollToRowKeyFound', {
|
||||||
|
detail: {
|
||||||
|
rowNumber: r,
|
||||||
|
scrollToRowKey: scrollToRowKey,
|
||||||
|
selectedRowKey: selectedRowKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [scrollToRowKey, selectedRowKey]);
|
||||||
|
|
||||||
// console.log('Gridler:Debug:Computer', {
|
// console.log('Gridler:Debug:Computer', {
|
||||||
// colFilters,
|
// colFilters,
|
||||||
// colOrder,
|
// colOrder,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export type FilterOptionOperator =
|
|||||||
| 'startswith';
|
| 'startswith';
|
||||||
|
|
||||||
export interface GridlerProps extends PropsWithChildren {
|
export interface GridlerProps extends PropsWithChildren {
|
||||||
askAPIRowNumber?: (key: string) => Promise<number>;
|
allowMultiSelect?: boolean;
|
||||||
columns?: GridlerColumns;
|
columns?: GridlerColumns;
|
||||||
|
|
||||||
defaultSort?: Array<SortOption>;
|
defaultSort?: Array<SortOption>;
|
||||||
@@ -89,25 +89,43 @@ export interface GridlerProps extends PropsWithChildren {
|
|||||||
) => GridCell;
|
) => GridCell;
|
||||||
|
|
||||||
rowHeight?: number;
|
rowHeight?: number;
|
||||||
|
scrollToRowKey?: number;
|
||||||
|
searchFields?: Array<string>;
|
||||||
|
searchStr?: string;
|
||||||
sections?: {
|
sections?: {
|
||||||
bottom?: React.ReactNode;
|
bottom?: React.ReactNode;
|
||||||
left?: React.ReactNode;
|
left?: React.ReactNode;
|
||||||
right?: React.ReactNode;
|
right?: React.ReactNode;
|
||||||
|
rightElementDisabled?: boolean;
|
||||||
rightElementEnd?: React.ReactNode;
|
rightElementEnd?: React.ReactNode;
|
||||||
rightElementStart?: React.ReactNode;
|
rightElementStart?: React.ReactNode;
|
||||||
top?: React.ReactNode;
|
top?: React.ReactNode;
|
||||||
};
|
};
|
||||||
selectedRow?: number;
|
selectedRowKey?: number;
|
||||||
|
selectFirstRowOnMount?: boolean;
|
||||||
selectMode?: 'cell' | 'row';
|
selectMode?: 'cell' | 'row';
|
||||||
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
||||||
|
title?: string;
|
||||||
tooltipBarProps?: React.HTMLAttributes<HTMLDivElement>;
|
tooltipBarProps?: React.HTMLAttributes<HTMLDivElement>;
|
||||||
total_rows?: number;
|
total_rows?: number;
|
||||||
|
|
||||||
uniqueid: string;
|
uniqueid: string;
|
||||||
useAPIQuery?: (index: number) => Promise<Array<Record<string, any>>>;
|
|
||||||
values?: Array<Record<string, any>>;
|
values?: Array<Record<string, any>>;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GridlerRef {
|
||||||
|
getGlideRef: () => DataEditorRef | undefined;
|
||||||
|
getState: GridlerState['getState'];
|
||||||
|
isEmpty: () => boolean;
|
||||||
|
refresh: (parms?: any) => Promise<void>;
|
||||||
|
reload: (parms?: any) => Promise<void>;
|
||||||
|
reloadRow: (key: number | string) => Promise<void>;
|
||||||
|
scrollToRow: (key: number | string) => Promise<void>;
|
||||||
|
selectRow: (key: number | string) => Promise<void>;
|
||||||
|
setStateFN: GridlerState['setStateFN'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GridlerState {
|
export interface GridlerState {
|
||||||
_active_requests?: Array<{ controller: AbortController; page: number }>;
|
_active_requests?: Array<{ controller: AbortController; page: number }>;
|
||||||
_activeTooltip?: ReactNode;
|
_activeTooltip?: ReactNode;
|
||||||
@@ -117,32 +135,37 @@ export interface GridlerState {
|
|||||||
_gridSelectionRows?: GridSelection['rows'];
|
_gridSelectionRows?: GridSelection['rows'];
|
||||||
_loadingList: CompactSelection;
|
_loadingList: CompactSelection;
|
||||||
_page_data: Record<number, Array<any>>;
|
_page_data: Record<number, Array<any>>;
|
||||||
|
_refresh: () => Promise<void>;
|
||||||
_scrollTimeout?: any | number;
|
_scrollTimeout?: any | number;
|
||||||
_visibleArea: Rectangle;
|
_visibleArea: Rectangle;
|
||||||
_visiblePages: Rectangle;
|
_visiblePages: Rectangle;
|
||||||
|
|
||||||
addError: (err: string, ...args: Array<any>) => void;
|
addError: (err: string, ...args: Array<any>) => void;
|
||||||
|
askAPIRowNumber?: (key: string) => Promise<number>;
|
||||||
colFilters?: Array<FilterOption>;
|
colFilters?: Array<FilterOption>;
|
||||||
colOrder?: Record<string, number>;
|
colOrder?: Record<string, number>;
|
||||||
colSize?: Record<string, number>;
|
colSize?: Record<string, number>;
|
||||||
colSort?: Array<SortOption>;
|
colSort?: Array<SortOption>;
|
||||||
data?: Array<any>;
|
data?: Array<any>;
|
||||||
|
|
||||||
errors: Array<string>;
|
errors: Array<string>;
|
||||||
focused?: boolean;
|
focused?: boolean;
|
||||||
|
|
||||||
get: () => GridlerState;
|
get: () => GridlerState;
|
||||||
getCellContent: (cell: Item) => GridCell;
|
getCellContent: (cell: Item) => GridCell;
|
||||||
getCellsForSelection: (
|
getCellsForSelection: (
|
||||||
selection: Rectangle,
|
selection: Rectangle,
|
||||||
abortSignal: AbortSignal
|
abortSignal: AbortSignal
|
||||||
) => CellArray | GetCellsThunk;
|
) => CellArray | GetCellsThunk;
|
||||||
|
getGridSelectedRows: () => Array<any>;
|
||||||
getRowBuffer: (row: number) => Record<string, any>;
|
getRowBuffer: (row: number) => Record<string, any>;
|
||||||
|
getRowIndexByKey: (key: number | string) => Promise<number | undefined>;
|
||||||
getState: <K extends keyof GridlerStoreState>(key: K) => GridlerStoreState[K];
|
getState: <K extends keyof GridlerStoreState>(key: K) => GridlerStoreState[K];
|
||||||
|
|
||||||
hasLocalData: boolean;
|
hasLocalData: boolean;
|
||||||
|
isEmpty: boolean;
|
||||||
|
|
||||||
loadingData?: boolean;
|
loadingData?: boolean;
|
||||||
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
|
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
|
||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
|
onCellActivated: (cell: Item) => void;
|
||||||
onCellClicked: (cell: Item, event: CellClickedEventArgs) => void;
|
onCellClicked: (cell: Item, event: CellClickedEventArgs) => void;
|
||||||
onCellEdited: (cell: Item, newVal: EditableGridCell) => void;
|
onCellEdited: (cell: Item, newVal: EditableGridCell) => void;
|
||||||
onColumnMoved: (from: number, to: number) => void;
|
onColumnMoved: (from: number, to: number) => void;
|
||||||
@@ -157,7 +180,6 @@ export interface GridlerState {
|
|||||||
onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => void;
|
onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => void;
|
||||||
onHeaderMenuClick: (col: number, screenPosition: Rectangle) => void;
|
onHeaderMenuClick: (col: number, screenPosition: Rectangle) => void;
|
||||||
onItemHovered: (args: GridMouseEventArgs) => void;
|
onItemHovered: (args: GridMouseEventArgs) => void;
|
||||||
|
|
||||||
onVisibleRegionChanged: (
|
onVisibleRegionChanged: (
|
||||||
r: Rectangle,
|
r: Rectangle,
|
||||||
tx: number,
|
tx: number,
|
||||||
@@ -170,8 +192,11 @@ export interface GridlerState {
|
|||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
refreshCells: (fromRow?: number, toRow?: number, col?: number) => void;
|
||||||
reload?: () => Promise<void>;
|
reload?: () => Promise<void>;
|
||||||
|
|
||||||
renderColumns?: GridlerColumns;
|
renderColumns?: GridlerColumns;
|
||||||
setState: <K extends keyof GridlerStoreState>(
|
setState: <K extends keyof GridlerStoreState>(
|
||||||
key: K,
|
key: K,
|
||||||
@@ -182,6 +207,7 @@ export interface GridlerState {
|
|||||||
value: (current: GridlerStoreState[K]) => Partial<GridlerStoreState[K]>
|
value: (current: GridlerStoreState[K]) => Partial<GridlerStoreState[K]>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
toCell: <TRowType extends Record<string, string>>(row: TRowType, col: number) => GridCell;
|
toCell: <TRowType extends Record<string, string>>(row: TRowType, col: number) => GridCell;
|
||||||
|
useAPIQuery?: (index: number) => Promise<Array<Record<string, any>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GridlerStoreState = GridlerProps & GridlerState;
|
export type GridlerStoreState = GridlerProps & GridlerState;
|
||||||
@@ -193,6 +219,12 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
_events: new EventTarget(),
|
_events: new EventTarget(),
|
||||||
_loadingList: CompactSelection.empty(),
|
_loadingList: CompactSelection.empty(),
|
||||||
_page_data: {},
|
_page_data: {},
|
||||||
|
_refresh: async () => {
|
||||||
|
const s = get();
|
||||||
|
await s.loadPage(0, 'all');
|
||||||
|
await s.refreshCells();
|
||||||
|
await s.reload?.();
|
||||||
|
},
|
||||||
_visibleArea: { height: 10000, width: 1000, x: 0, y: 0 },
|
_visibleArea: { height: 10000, width: 1000, x: 0, y: 0 },
|
||||||
_visiblePages: { height: 0, width: 0, x: 0, y: 0 },
|
_visiblePages: { height: 0, width: 0, x: 0, y: 0 },
|
||||||
addError: (err: string, ...args: Array<unknown>) => {
|
addError: (err: string, ...args: Array<unknown>) => {
|
||||||
@@ -246,6 +278,42 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
return result as CellArray;
|
return result as CellArray;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getGridSelectedRows: () => {
|
||||||
|
const state = get();
|
||||||
|
const buffers: Array<any> = [];
|
||||||
|
const page_data = state._page_data;
|
||||||
|
const pageSize = state.pageSize;
|
||||||
|
|
||||||
|
if (state._gridSelectionRows) {
|
||||||
|
for (const range of state._gridSelectionRows) {
|
||||||
|
let buffer = undefined;
|
||||||
|
|
||||||
|
for (const p in page_data) {
|
||||||
|
for (const r in page_data[p]) {
|
||||||
|
const idx = Number(p) * pageSize + Number(r);
|
||||||
|
if (isNaN(idx)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(page_data[p][r]?._rownumber) === range + 1) {
|
||||||
|
buffer = page_data[p][r];
|
||||||
|
//console.log('Found row', range, idx, page_data[p][r]?._rownumber);
|
||||||
|
break;
|
||||||
|
} else if (idx === range + 1) {
|
||||||
|
buffer = page_data[p][r];
|
||||||
|
//console.log('Found row 2', range, idx, page_data[p][r]?._rownumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buffer !== undefined) {
|
||||||
|
buffers.push(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffers;
|
||||||
|
},
|
||||||
|
|
||||||
getRowBuffer: (row: number) => {
|
getRowBuffer: (row: number) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
//Handle local data
|
//Handle local data
|
||||||
@@ -268,10 +336,48 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
|
|
||||||
return rowData;
|
return rowData;
|
||||||
},
|
},
|
||||||
|
getRowIndexByKey: async (key: number | string) => {
|
||||||
|
const state = get();
|
||||||
|
|
||||||
|
let rowIndex = -1;
|
||||||
|
if (state.ready) {
|
||||||
|
const page_data = state._page_data;
|
||||||
|
const pageSize = state.pageSize;
|
||||||
|
const keyField = state.keyField ?? 'id';
|
||||||
|
for (const p in page_data) {
|
||||||
|
for (const r in page_data[p]) {
|
||||||
|
const idx = Number(p) * pageSize + Number(r);
|
||||||
|
//console.log('Found row', idx, page_data[p][r]?.[keyField], scrollToRowKey);
|
||||||
|
if (String(page_data[p][r]?.[keyField]) === String(key)) {
|
||||||
|
rowIndex =
|
||||||
|
page_data[p][r]?._rownumber > 0 ? page_data[p][r]?._rownumber : idx > 0 ? idx : -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rowIndex > 0) {
|
||||||
|
console.log('Local row index', rowIndex, key);
|
||||||
|
return rowIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowIndex > 0) {
|
||||||
|
return rowIndex;
|
||||||
|
} else if (typeof state.askAPIRowNumber === 'function') {
|
||||||
|
const rn = await state.askAPIRowNumber(String(key));
|
||||||
|
if (rn && rn >= 0) {
|
||||||
|
console.log('Remote row index', rowIndex, key);
|
||||||
|
return rn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
getState: (key) => {
|
getState: (key) => {
|
||||||
return get()[key];
|
return get()[key];
|
||||||
},
|
},
|
||||||
hasLocalData: false,
|
hasLocalData: false,
|
||||||
|
isEmpty: true,
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
loadPage: async (pPage: number, clearMode?: 'all' | 'page') => {
|
loadPage: async (pPage: number, clearMode?: 'all' | 'page') => {
|
||||||
const state = get();
|
const state = get();
|
||||||
@@ -330,7 +436,7 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn('loadPage Error: ', page, e);
|
console.error('loadPage Error: ', page, e);
|
||||||
state._events.dispatchEvent(
|
state._events.dispatchEvent(
|
||||||
new CustomEvent('loadPage_error', {
|
new CustomEvent('loadPage_error', {
|
||||||
detail: { clearMode, error: e, page: pPage, state },
|
detail: { clearMode, error: e, page: pPage, state },
|
||||||
@@ -341,10 +447,29 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
},
|
},
|
||||||
maxConcurrency: 1,
|
maxConcurrency: 1,
|
||||||
mounted: false,
|
mounted: false,
|
||||||
|
onCellActivated: (cell: Item): void => {
|
||||||
|
const state = get();
|
||||||
|
const [col, row] = cell;
|
||||||
|
|
||||||
|
state._events.dispatchEvent(
|
||||||
|
new CustomEvent('onCellActivated', {
|
||||||
|
detail: { cell, col, row, state },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
state.glideProps?.onCellActivated?.(cell);
|
||||||
|
},
|
||||||
onCellClicked: (cell: Item, event: CellClickedEventArgs) => {
|
onCellClicked: (cell: Item, event: CellClickedEventArgs) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
const [col, row] = cell;
|
const [col, row] = cell;
|
||||||
state.glideProps?.onCellClicked?.(cell, event);
|
if (state.glideProps?.onCellClicked) {
|
||||||
|
state.glideProps?.onCellClicked?.(cell, event);
|
||||||
|
}
|
||||||
|
if (state.values?.length) {
|
||||||
|
if (state.onChange) {
|
||||||
|
state.onChange(state.values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state._events.dispatchEvent(
|
state._events.dispatchEvent(
|
||||||
new CustomEvent('onCellClicked', {
|
new CustomEvent('onCellClicked', {
|
||||||
detail: { cell, col, row, state },
|
detail: { cell, col, row, state },
|
||||||
@@ -395,6 +520,7 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
onColumnResize: (
|
onColumnResize: (
|
||||||
column: GridColumn,
|
column: GridColumn,
|
||||||
newSize: number,
|
newSize: number,
|
||||||
@@ -419,20 +545,16 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onContextClick: (area: string, event: CellClickedEventArgs, col?: number, row?: number) => {
|
onContextClick: (area: string, event: CellClickedEventArgs, col?: number, row?: number) => {
|
||||||
const s = get();
|
const s = get();
|
||||||
const coldef = s.renderColumns?.[col ?? -1];
|
const coldef = s.renderColumns?.[col ?? -1];
|
||||||
|
|
||||||
const items =
|
const items =
|
||||||
area === 'menu'
|
area === 'menu'
|
||||||
? [
|
? [{ leftSection: <IconGrid4x4 size={16} />, title: s.title ?? 'Grid' }]
|
||||||
{
|
|
||||||
label: `Side menu`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: coldef
|
: coldef
|
||||||
? [
|
? [
|
||||||
|
{ leftSection: <IconGrid4x4 size={16} />, title: s.title ?? 'Grid' },
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -602,9 +724,11 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
isDivider: true,
|
isDivider: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'refesh',
|
||||||
label: `Refresh`,
|
label: `Refresh`,
|
||||||
onClickAsync: async () => {
|
onClick: () => {
|
||||||
await s.reload?.();
|
const s = get();
|
||||||
|
s._refresh?.();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -701,6 +825,30 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
},
|
},
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
ready: false,
|
ready: false,
|
||||||
|
refreshCells: (fromRow?: number, toRow?: number, col?: number) => {
|
||||||
|
const state = get();
|
||||||
|
const damageList: { cell: [number, number] }[] = [];
|
||||||
|
const colLen = Object.keys(state.renderColumns ?? [1, 2, 3]).length;
|
||||||
|
|
||||||
|
const from = fromRow && fromRow > 0 ? fromRow : 0;
|
||||||
|
const to = toRow && toRow >= from ? toRow : from + state.pageSize;
|
||||||
|
|
||||||
|
for (let row = from; row <= to; row++) {
|
||||||
|
if (col && col > 0) {
|
||||||
|
damageList.push({
|
||||||
|
cell: [col, row],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (let c = 0; c <= colLen; c++) {
|
||||||
|
damageList.push({
|
||||||
|
cell: [c, row],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state._glideref?.updateCells(damageList);
|
||||||
|
},
|
||||||
setState: (key, value) => {
|
setState: (key, value) => {
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
@@ -757,8 +905,8 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
allowOverlay: true,
|
allowOverlay: true,
|
||||||
data: val,
|
data: val ?? '',
|
||||||
displayData: String(val),
|
displayData: String(val ?? ''),
|
||||||
kind: GridCellKind.Text,
|
kind: GridCellKind.Text,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -815,63 +963,18 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
};
|
};
|
||||||
}, [setState, getState]);
|
}, [setState, getState]);
|
||||||
|
|
||||||
/// logic to apply the selected row.
|
|
||||||
useEffect(() => {
|
|
||||||
const ready = getState('ready');
|
|
||||||
const ref = getState('_glideref');
|
|
||||||
const keyField = getState('keyField') ?? 'id';
|
|
||||||
const selectedRow = getState('selectedRow') ?? props.selectedRow;
|
|
||||||
const askAPIRowNumber = getState('askAPIRowNumber');
|
|
||||||
let rowIndex = -1;
|
|
||||||
if (selectedRow && ref && ready) {
|
|
||||||
const page_data = getState('_page_data');
|
|
||||||
const pageSize = getState('pageSize');
|
|
||||||
for (const p in page_data) {
|
|
||||||
for (const r in page_data[p]) {
|
|
||||||
const idx = Number(p) * pageSize + Number(r);
|
|
||||||
//console.log('Found row', idx, page_data[p][r]?.[keyField], selectedRow);
|
|
||||||
if (String(page_data[p][r]?.[keyField]) === String(selectedRow)) {
|
|
||||||
rowIndex =
|
|
||||||
page_data[p][r]?._rownumber > 0 ? page_data[p][r]?._rownumber : idx > 0 ? idx : -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rowIndex > 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowIndex > 0) {
|
|
||||||
ref.scrollTo(0, rowIndex);
|
|
||||||
} else if (typeof askAPIRowNumber === 'function') {
|
|
||||||
askAPIRowNumber(String(selectedRow))
|
|
||||||
.then((r) => {
|
|
||||||
if (r >= 0) {
|
|
||||||
ref.scrollTo(0, r);
|
|
||||||
getState('_events').dispatchEvent(
|
|
||||||
new CustomEvent('selectedRowFound', {
|
|
||||||
detail: { rowNumber: r, selectedRow: selectedRow },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('Error in askAPIRowNumber', e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [props.selectedRow]);
|
|
||||||
|
|
||||||
getState('_events').addEventListener('reload', (_e: Event) => {
|
getState('_events').addEventListener('reload', (_e: Event) => {
|
||||||
getState('reload')?.();
|
getState('_refresh')?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
|
|
||||||
|
colSort: props.defaultSort ?? getState('colSort') ?? [],
|
||||||
hideMenu: props.hideMenu ?? menus.hide,
|
hideMenu: props.hideMenu ?? menus.hide,
|
||||||
|
scrollToRowKey: props.scrollToRowKey ?? props.selectedRowKey ?? getState('scrollToRowKey'),
|
||||||
showMenu: props.showMenu ?? menus.show,
|
showMenu: props.showMenu ?? menus.show,
|
||||||
total_rows: props.total_rows ?? getState('total_rows') ?? 0,
|
total_rows: getState('total_rows') ?? props.total_rows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useGridlerStore } from './GridlerStore';
|
|||||||
export const Pager = React.memo(() => {
|
export const Pager = React.memo(() => {
|
||||||
const [
|
const [
|
||||||
setState,
|
setState,
|
||||||
|
getState,
|
||||||
glideref,
|
glideref,
|
||||||
visiblePages,
|
visiblePages,
|
||||||
//_visibleArea,
|
//_visibleArea,
|
||||||
@@ -16,6 +17,7 @@ export const Pager = React.memo(() => {
|
|||||||
hasLocalData,
|
hasLocalData,
|
||||||
] = useGridlerStore((s) => [
|
] = useGridlerStore((s) => [
|
||||||
s.setState,
|
s.setState,
|
||||||
|
s.getState,
|
||||||
s._glideref,
|
s._glideref,
|
||||||
s._visiblePages,
|
s._visiblePages,
|
||||||
//s._visibleArea,
|
//s._visibleArea,
|
||||||
@@ -38,10 +40,10 @@ export const Pager = React.memo(() => {
|
|||||||
if (!glideref) {
|
if (!glideref) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasLocalData) {
|
// if (hasLocalData) {
|
||||||
//using local data, no need to load pages
|
// //using local data, no need to load pages
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
const firstPage = Math.max(0, Math.floor(visiblePages.y / pageSize));
|
const firstPage = Math.max(0, Math.floor(visiblePages.y / pageSize));
|
||||||
const lastPage = Math.floor((visiblePages.y + visiblePages.height) / pageSize);
|
const lastPage = Math.floor((visiblePages.y + visiblePages.height) / pageSize);
|
||||||
//const upperPage = pageSize * firstPage;
|
//const upperPage = pageSize * firstPage;
|
||||||
@@ -57,7 +59,10 @@ export const Pager = React.memo(() => {
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
for (const page of range(firstPage, lastPage + 1, 1)) {
|
for (const page of range(firstPage, lastPage + 1, 1)) {
|
||||||
loadPage(page);
|
loadPage(page).then(() => {
|
||||||
|
const pg = getState('_page_data')?.[0] ?? {};
|
||||||
|
setState('isEmpty', pg && pg.length > 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [loadPage, pageSize, visiblePages, glideref, _loadingList, hasLocalData]);
|
}, [loadPage, pageSize, visiblePages, glideref, _loadingList, hasLocalData]);
|
||||||
|
|
||||||
|
|||||||
55
src/Gridler/components/RefHandler.tsx
Normal file
55
src/Gridler/components/RefHandler.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { type PropsWithChildren, type Ref, useImperativeHandle } from 'react';
|
||||||
|
|
||||||
|
import { type GridlerRef, useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
|
function _GridlerRefHandler(props: PropsWithChildren, ref: Ref<GridlerRef> | undefined) {
|
||||||
|
const [setStateFN, getstate] = useGridlerStore((s) => [s.setStateFN, s.getState]);
|
||||||
|
|
||||||
|
useImperativeHandle<GridlerRef, GridlerRef>(ref, () => {
|
||||||
|
return {
|
||||||
|
getGlideRef: () => {
|
||||||
|
return getstate('_glideref');
|
||||||
|
},
|
||||||
|
getState: getstate,
|
||||||
|
isEmpty: () => getstate('isEmpty'),
|
||||||
|
refresh: async (parms?: any) => {
|
||||||
|
const refreshCells = getstate('refreshCells');
|
||||||
|
const loadPage = getstate('loadPage');
|
||||||
|
loadPage?.(parms?.pageIndex ?? 0, 'all').then(() => {
|
||||||
|
refreshCells?.();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reload: async (parms?: any) => {
|
||||||
|
const refreshCells = getstate('refreshCells');
|
||||||
|
const loadPage = getstate('loadPage');
|
||||||
|
loadPage?.(parms?.pageIndex ?? 0, 'all').then(() => {
|
||||||
|
refreshCells?.();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reloadRow: async (key: number | string) => {
|
||||||
|
const refreshCells = getstate('refreshCells');
|
||||||
|
//const loadPage = getstate('loadPage');
|
||||||
|
const getRowIndexByKey = getstate('getRowIndexByKey');
|
||||||
|
const rn = await getRowIndexByKey?.(String(key));
|
||||||
|
if (rn && rn >= 0) {
|
||||||
|
refreshCells?.(rn, rn + 1);
|
||||||
|
//todo loadpage or row from server
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollToRow: async (key: number | string) => {
|
||||||
|
if (key && Number(key) >= 0) {
|
||||||
|
setStateFN('scrollToRowKey', (cv) => Number(key ?? cv));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectRow: async (key: number | string) => {
|
||||||
|
if (key && Number(key) >= 0) {
|
||||||
|
setStateFN('selectedRowKey', (cv) => Number(key ?? cv));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStateFN: setStateFN,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return <>{props.children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GridlerRefHandler = React.forwardRef(_GridlerRefHandler);
|
||||||
@@ -3,7 +3,7 @@ import { IconMenu2 } from '@tabler/icons-react';
|
|||||||
|
|
||||||
import { useGridlerStore } from './GridlerStore';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
export function RightMenuIcon() {
|
export function GridlerRightMenuIcon() {
|
||||||
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
||||||
loadingData: s.loadingData,
|
loadingData: s.loadingData,
|
||||||
onContextClick: s.onContextClick,
|
onContextClick: s.onContextClick,
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import type { APIOptions } from '../../utils/types';
|
import type { APIOptions } from '../../utils/types';
|
||||||
|
import type { GridlerColumn } from '../Column';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type FetchAPIOperation,
|
||||||
|
GoAPIHeaders,
|
||||||
|
type GoAPIOperation,
|
||||||
|
} from '../../utils/golang-restapi-v2';
|
||||||
import { useGridlerStore } from '../GridlerStore';
|
import { useGridlerStore } from '../GridlerStore';
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
export interface GlidlerAPIAdaptorForGoLangv2Props<T = unknown> extends APIOptions {
|
||||||
export const GlidlerAPIAdaptorForGoLangv2 = React.memo((props: APIOptions) => {
|
filter?: string;
|
||||||
|
hotfields?: Array<string>;
|
||||||
|
initialData?: Array<T>;
|
||||||
|
options?: Array<GoAPIOperation>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _GlidlerAPIAdaptorForGoLangv2<T = unknown>(props: GlidlerAPIAdaptorForGoLangv2Props<T>) {
|
||||||
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
||||||
s.setStateFN,
|
s.setStateFN,
|
||||||
s.setState,
|
s.setState,
|
||||||
@@ -15,30 +27,188 @@ export const GlidlerAPIAdaptorForGoLangv2 = React.memo((props: APIOptions) => {
|
|||||||
s.mounted,
|
s.mounted,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
const useAPIQuery: (index: number) => Promise<any> = useCallback(
|
||||||
const colSort = getState('colSort');
|
async (index: number) => {
|
||||||
const pageSize = getState('pageSize');
|
const columns = getState('columns');
|
||||||
const colFilters = getState('colFilters');
|
const colSort = getState('colSort');
|
||||||
const _active_requests = getState('_active_requests');
|
const pageSize = getState('pageSize');
|
||||||
setState('loadingData', true);
|
const colFilters = getState('colFilters');
|
||||||
try {
|
const searchStr = getState('searchStr');
|
||||||
|
const searchFields = getState('searchFields');
|
||||||
|
const _active_requests = getState('_active_requests');
|
||||||
|
setState('loadingData', true);
|
||||||
|
try {
|
||||||
|
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
||||||
|
if (props && props.url) {
|
||||||
|
const head = new Headers();
|
||||||
|
|
||||||
|
head.set('Authorization', `Token ${props.authtoken}`);
|
||||||
|
const ops: FetchAPIOperation[] = [
|
||||||
|
{ type: 'limit', value: String(pageSize ?? 50) },
|
||||||
|
{ type: 'offset', value: String((pageSize ?? 50) * index) },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (colSort?.length && colSort.length > 0) {
|
||||||
|
ops.push({
|
||||||
|
type: 'sort',
|
||||||
|
value: colSort
|
||||||
|
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
||||||
|
.reduce((acc: any, val: any) => `${acc},${val}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
colFilters
|
||||||
|
?.filter((f) => f.value?.length > 0)
|
||||||
|
?.forEach((filter: any) => {
|
||||||
|
if (filter.value && filter.value !== '') {
|
||||||
|
ops.push({
|
||||||
|
name: `${filter.id}`,
|
||||||
|
op: filter.operator,
|
||||||
|
type: 'searchop',
|
||||||
|
value: filter.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchStr && searchStr !== '') {
|
||||||
|
columns
|
||||||
|
?.filter(
|
||||||
|
(f) =>
|
||||||
|
!f.disableFilter &&
|
||||||
|
!f.disableSearch &&
|
||||||
|
!f.virtual &&
|
||||||
|
f.id &&
|
||||||
|
((searchFields ?? []).length == 0 || searchFields?.includes(f.id))
|
||||||
|
)
|
||||||
|
?.forEach((filter: any) => {
|
||||||
|
ops.push({
|
||||||
|
name: `${filter.id ?? ""}`,
|
||||||
|
op: 'contains',
|
||||||
|
type: 'searchor',
|
||||||
|
value: searchStr,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.filter && props.filter !== '') {
|
||||||
|
ops.push({
|
||||||
|
name: 'sql_filter',
|
||||||
|
type: 'custom-sql-w',
|
||||||
|
value: props.filter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((props.options ?? []).length > 0) {
|
||||||
|
ops.push(...(props.options ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
const col_ids =
|
||||||
|
columns
|
||||||
|
?.filter((col) => !col.virtual)
|
||||||
|
?.map((col: GridlerColumn) => {
|
||||||
|
return col.id;
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
if (props.hotfields && props.hotfields.length > 0) {
|
||||||
|
col_ids?.push(props.hotfields.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col_ids && col_ids.length > 0) {
|
||||||
|
ops.push({
|
||||||
|
type: 'select-fields',
|
||||||
|
value: col_ids.join(','),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops && ops.length > 0) {
|
||||||
|
const optionHeaders = GoAPIHeaders(ops);
|
||||||
|
for (const oh in GoAPIHeaders(ops)) {
|
||||||
|
head.set(oh, optionHeaders[oh]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1;
|
||||||
|
_active_requests?.forEach((r) => {
|
||||||
|
if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) {
|
||||||
|
r.controller?.abort?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
_active_requests &&
|
||||||
|
currentRequestIndex >= 0 &&
|
||||||
|
_active_requests[currentRequestIndex]
|
||||||
|
) {
|
||||||
|
//console.log(`Already queued ${index}`, index, s._active_requests);
|
||||||
|
setState('loadingData', false);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
await setStateFN('_active_requests', (cv) => [
|
||||||
|
...(cv ?? []),
|
||||||
|
{ controller, page: index },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
||||||
|
{
|
||||||
|
headers: head,
|
||||||
|
method: 'GET',
|
||||||
|
signal: controller?.signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const cr = res.headers.get('Content-Range')?.split('/');
|
||||||
|
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
||||||
|
setState('total_rows', parseInt(cr[1], 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
setState('loadingData', false);
|
||||||
|
return data ?? [];
|
||||||
|
}
|
||||||
|
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
||||||
|
|
||||||
|
await setStateFN('_active_requests', (cv) => [
|
||||||
|
...(cv ?? []).filter((f) => f.page !== index),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (_e) {
|
||||||
|
//console.log('APIAdaptorGoLangv2 error', e);
|
||||||
|
//addError(`Error: ${e}`, 'api', props.url);
|
||||||
|
}
|
||||||
|
setState('loadingData', false);
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
getState,
|
||||||
|
props.authtoken,
|
||||||
|
props.url,
|
||||||
|
props.filter,
|
||||||
|
JSON.stringify(props.options),
|
||||||
|
setState,
|
||||||
|
setStateFN,
|
||||||
|
addError,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const askAPIRowNumber: (key: string) => Promise<number> = useCallback(
|
||||||
|
async (key: string) => {
|
||||||
|
const colFilters = getState('colFilters');
|
||||||
|
|
||||||
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
||||||
if (props && props.url) {
|
if (props && props.url) {
|
||||||
const head = new Headers();
|
const head = new Headers();
|
||||||
head.set('x-limit', String(pageSize ?? 50));
|
const ops: FetchAPIOperation[] = [
|
||||||
head.set('x-offset', String((pageSize ?? 50) * index));
|
{ type: 'limit', value: String(10) },
|
||||||
|
{ type: 'fetch-rownumber', value: key },
|
||||||
|
];
|
||||||
|
|
||||||
head.set('Authorization', `Token ${props.authtoken}`);
|
head.set('Authorization', `Token ${props.authtoken}`);
|
||||||
|
|
||||||
if (colSort?.length && colSort.length > 0) {
|
|
||||||
head.set(
|
|
||||||
'x-sort',
|
|
||||||
colSort
|
|
||||||
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
|
||||||
.reduce((acc: any, val: any) => `${acc},${val}`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colFilters?.length && colFilters.length > 0) {
|
if (colFilters?.length && colFilters.length > 0) {
|
||||||
colFilters
|
colFilters
|
||||||
?.filter((f) => f.value?.length > 0)
|
?.filter((f) => f.value?.length > 0)
|
||||||
@@ -49,99 +219,70 @@ export const GlidlerAPIAdaptorForGoLangv2 = React.memo((props: APIOptions) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1;
|
if (props.filter && props.filter !== '') {
|
||||||
_active_requests?.forEach((r) => {
|
ops.push({
|
||||||
if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) {
|
name: 'sql_filter',
|
||||||
r.controller?.abort?.();
|
type: 'custom-sql-w',
|
||||||
|
value: props.filter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.options && props.options.length > 0) {
|
||||||
|
const optionHeaders = GoAPIHeaders(props.options);
|
||||||
|
for (const oh in optionHeaders) {
|
||||||
|
head.set(oh, optionHeaders[oh]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops && ops.length > 0) {
|
||||||
|
const optionHeaders = GoAPIHeaders(ops);
|
||||||
|
for (const oh in GoAPIHeaders(ops)) {
|
||||||
|
head.set(oh, optionHeaders[oh]);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (_active_requests && currentRequestIndex >= 0 && _active_requests[currentRequestIndex]) {
|
|
||||||
//console.log(`Already queued ${index}`, index, s._active_requests);
|
|
||||||
setState('loadingData', false);
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []), { controller, page: index }]);
|
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(`${props.url}?x-fetch-rownumber=${key}}`, {
|
||||||
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
headers: head,
|
||||||
{
|
method: 'GET',
|
||||||
headers: head,
|
signal: controller?.signal,
|
||||||
method: 'GET',
|
});
|
||||||
signal: controller?.signal,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const cr = res.headers.get('Content-Range')?.split('/');
|
|
||||||
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
|
||||||
setState('total_rows', parseInt(cr[1], 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setState('loadingData', false);
|
|
||||||
return data ?? [];
|
return data?.[0]?._rownumber ?? data?._rownumber ?? 0;
|
||||||
}
|
}
|
||||||
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
||||||
|
|
||||||
await setStateFN('_active_requests', (cv) => [
|
|
||||||
...(cv ?? []).filter((f) => f.page !== index),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
return [];
|
||||||
//console.log('APIAdaptorGoLangv2 error', e);
|
},
|
||||||
addError(`Error: ${e}`, 'api', props.url);
|
[props.url, props.authtoken, props.filter, props.options, getState, addError]
|
||||||
}
|
);
|
||||||
setState('loadingData', false);
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const askAPIRowNumber: (key: string) => Promise<number> = async (key: string) => {
|
|
||||||
const colFilters = getState('colFilters');
|
|
||||||
|
|
||||||
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
|
||||||
if (props && props.url) {
|
|
||||||
const head = new Headers();
|
|
||||||
head.set('x-limit', '10');
|
|
||||||
head.set('x-fetch-rownumber', String(key));
|
|
||||||
|
|
||||||
head.set('Authorization', `Token ${props.authtoken}`);
|
|
||||||
|
|
||||||
if (colFilters?.length && colFilters.length > 0) {
|
|
||||||
colFilters
|
|
||||||
?.filter((f) => f.value?.length > 0)
|
|
||||||
?.forEach((filter: any) => {
|
|
||||||
if (filter.value && filter.value !== '') {
|
|
||||||
head.set(`x-searchop-${filter.operator}-${filter.id}`, `${filter.value}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
const res = await fetch(`${props.url}?x-fetch-rownumber=${key}}`, {
|
|
||||||
headers: head,
|
|
||||||
method: 'GET',
|
|
||||||
signal: controller?.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
return data?.[0]?._rownumber ?? data?._rownumber ?? 0;
|
|
||||||
}
|
|
||||||
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
//Reset the function in the store.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState('useAPIQuery', useAPIQuery);
|
setState('useAPIQuery', useAPIQuery);
|
||||||
setState('askAPIRowNumber', askAPIRowNumber);
|
setState('askAPIRowNumber', askAPIRowNumber);
|
||||||
}, [props.url, props.authtoken, mounted, setState]);
|
|
||||||
|
const _refresh = getState('_refresh');
|
||||||
|
|
||||||
|
//Reset the loaded pages to new rules
|
||||||
|
_refresh?.().then(() => {
|
||||||
|
const onChange = getState('onChange');
|
||||||
|
const getGridSelectedRows = getState('getGridSelectedRows');
|
||||||
|
if (onChange && typeof onChange === 'function') {
|
||||||
|
const buffers = getGridSelectedRows?.();
|
||||||
|
onChange(buffers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [props.url, props.authtoken, props.filter, JSON.stringify(props.options), mounted, setState]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
|
export const GlidlerAPIAdaptorForGoLangv2 = React.memo(_GlidlerAPIAdaptorForGoLangv2);
|
||||||
|
|
||||||
GlidlerAPIAdaptorForGoLangv2.displayName = 'Gridler-GlidlerAPIAdaptorForGoLangv2';
|
GlidlerAPIAdaptorForGoLangv2.displayName = 'Gridler-GlidlerAPIAdaptorForGoLangv2';
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type { GridlerColumn } from '../Column';
|
|||||||
import { type GridlerProps, type GridlerState, useGridlerStore } from '../GridlerStore';
|
import { type GridlerProps, type GridlerState, useGridlerStore } from '../GridlerStore';
|
||||||
|
|
||||||
export function GlidlerFormAdaptor(props: {
|
export function GlidlerFormAdaptor(props: {
|
||||||
|
changeOnActiveClick?: boolean;
|
||||||
descriptionField?: ((data: Record<string, unknown>) => string) | string;
|
descriptionField?: ((data: Record<string, unknown>) => string) | string;
|
||||||
getMenuItems?: GridlerProps['getMenuItems'];
|
getMenuItems?: GridlerProps['getMenuItems'];
|
||||||
onReload?: () => void;
|
onReload?: () => void;
|
||||||
@@ -23,13 +24,35 @@ export function GlidlerFormAdaptor(props: {
|
|||||||
) => void;
|
) => void;
|
||||||
showDescriptionInMenu?: boolean;
|
showDescriptionInMenu?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [getState, mounted, setState, reload] = useGridlerStore((s) => [
|
const [getState, mounted, setState, _events] = useGridlerStore((s) => [
|
||||||
s.getState,
|
s.getState,
|
||||||
s.mounted,
|
s.mounted,
|
||||||
s.setState,
|
s.setState,
|
||||||
s.reload,
|
s._events,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mounted && props.changeOnActiveClick) {
|
||||||
|
const evf = (event: CustomEvent<any>) => {
|
||||||
|
const { row, state } = event.detail;
|
||||||
|
const getRowBuffer = state.getRowBuffer as (row: number) => Record<string, unknown>;
|
||||||
|
if (getRowBuffer) {
|
||||||
|
const rowData = getRowBuffer(row);
|
||||||
|
if (!rowData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.onRequestForm('change', rowData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_events?.addEventListener('onCellActivated', evf as any);
|
||||||
|
return () => {
|
||||||
|
if (evf) {
|
||||||
|
_events?.removeEventListener('onCellActivated', evf as any);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [props.changeOnActiveClick, mounted, _events]);
|
||||||
|
|
||||||
const getMenuItems = useCallback(
|
const getMenuItems = useCallback(
|
||||||
(
|
(
|
||||||
id: string,
|
id: string,
|
||||||
@@ -45,9 +68,9 @@ export function GlidlerFormAdaptor(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const items = [] as Array<MantineBetterMenuInstanceItem>;
|
const items = [] as Array<MantineBetterMenuInstanceItem>;
|
||||||
if (defaultItems && id === 'cell') {
|
|
||||||
items.push(...(defaultItems as Array<MantineBetterMenuInstanceItem>));
|
items.push(...(defaultItems as Array<MantineBetterMenuInstanceItem>));
|
||||||
}
|
|
||||||
const rows = getState('_gridSelection')?.rows.toArray() ?? [];
|
const rows = getState('_gridSelection')?.rows.toArray() ?? [];
|
||||||
const manyRows = rows.length > 1;
|
const manyRows = rows.length > 1;
|
||||||
|
|
||||||
@@ -140,8 +163,9 @@ export function GlidlerFormAdaptor(props: {
|
|||||||
c: 'orange',
|
c: 'orange',
|
||||||
label: 'Refresh',
|
label: 'Refresh',
|
||||||
leftSection: <IconRefresh color="orange" size={16} />,
|
leftSection: <IconRefresh color="orange" size={16} />,
|
||||||
onClick: () => {
|
onClickAsync: async () => {
|
||||||
reload?.();
|
const _refresh = getState('_refresh');
|
||||||
|
await _refresh?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,47 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { useGridlerStore } from '../GridlerStore';
|
import type { GridlerColumns } from '../Column';
|
||||||
|
|
||||||
export interface GlidlerLocalDataAdaptorProps {
|
import { type FilterOption, type SortOption, useGridlerStore } from '../GridlerStore';
|
||||||
data: Array<unknown>;
|
|
||||||
|
export interface GlidlerLocalDataAdaptorProps<T = unknown> {
|
||||||
|
data: Array<T>;
|
||||||
|
onColumnFilter?: (
|
||||||
|
colFilters: Array<FilterOption> | undefined,
|
||||||
|
cols: GridlerColumns | undefined,
|
||||||
|
data: Array<T>
|
||||||
|
) => Array<T>;
|
||||||
|
onColumnSort?: (
|
||||||
|
colSort: Array<SortOption> | undefined,
|
||||||
|
cols: GridlerColumns | undefined,
|
||||||
|
data: Array<T>
|
||||||
|
) => Array<T>;
|
||||||
|
onSearch?: (
|
||||||
|
searchField: string | undefined,
|
||||||
|
cols: GridlerColumns | undefined,
|
||||||
|
data: Array<T>
|
||||||
|
) => Array<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const GlidlerLocalDataAdaptor = React.memo((props: GlidlerLocalDataAdaptorProps) => {
|
function _GlidlerLocalDataAdaptor<T = unknown>(props: GlidlerLocalDataAdaptorProps<T>) {
|
||||||
const [setState, getState, mounted] = useGridlerStore((s) => [s.setState, s.getState, s.mounted]);
|
const [setState, getState, mounted] = useGridlerStore((s) => [s.setState, s.getState, s.mounted]);
|
||||||
|
|
||||||
|
const { colFilters, colSort, columns, searchStr } = useGridlerStore((s) => ({
|
||||||
|
colFilters: s.colFilters,
|
||||||
|
colOrder: s.colOrder,
|
||||||
|
colSize: s.colSize,
|
||||||
|
colSort: s.colSort,
|
||||||
|
columns: s.columns,
|
||||||
|
searchStr: s.searchStr,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const refChanged = React.useRef({
|
||||||
|
colFilters: colFilters,
|
||||||
|
colSort: colSort,
|
||||||
|
searchStr: searchStr,
|
||||||
|
});
|
||||||
|
|
||||||
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
||||||
const pageSize = getState('pageSize');
|
const pageSize = getState('pageSize');
|
||||||
|
|
||||||
@@ -25,7 +57,38 @@ export const GlidlerLocalDataAdaptor = React.memo((props: GlidlerLocalDataAdapto
|
|||||||
setState('useAPIQuery', useAPIQuery);
|
setState('useAPIQuery', useAPIQuery);
|
||||||
}, [mounted, setState]);
|
}, [mounted, setState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.onColumnSort && colSort !== refChanged?.current?.colSort) {
|
||||||
|
const sortedData = props.onColumnSort(colSort, columns, props.data as Array<T>);
|
||||||
|
setState('total_rows', sortedData.length);
|
||||||
|
setState('data', sortedData);
|
||||||
|
refChanged.current.colSort = colSort;
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
}
|
||||||
|
}, [colSort, props.onColumnSort]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.onColumnFilter && colFilters !== refChanged?.current?.colFilters) {
|
||||||
|
const filteredData = props.onColumnFilter(colFilters, columns, props.data as Array<T>);
|
||||||
|
setState('total_rows', filteredData.length);
|
||||||
|
setState('data', filteredData);
|
||||||
|
refChanged.current.colFilters = colFilters;
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
}
|
||||||
|
}, [colFilters, props.onColumnFilter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.onSearch && searchStr !== refChanged?.current?.searchStr) {
|
||||||
|
const filteredData = props.onSearch(searchStr, columns, props.data as Array<T>);
|
||||||
|
setState('total_rows', filteredData.length);
|
||||||
|
setState('data', filteredData);
|
||||||
|
refChanged.current.colFilters = colFilters;
|
||||||
|
getState('refreshCells')?.();
|
||||||
|
}
|
||||||
|
}, [searchStr, props.onSearch]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
}
|
||||||
|
export const GlidlerLocalDataAdaptor = React.memo(_GlidlerLocalDataAdaptor);
|
||||||
|
|
||||||
GlidlerLocalDataAdaptor.displayName = 'Gridler-GlidlerLocalDataAdaptor';
|
GlidlerLocalDataAdaptor.displayName = 'Gridler-GlidlerLocalDataAdaptor';
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export const useGridTheme = () => {
|
|||||||
// }[colorScheme];
|
// }[colorScheme];
|
||||||
|
|
||||||
|
|
||||||
// for (const selectedRow of gridSelection?.rows) {
|
// for (const scrollToRowKey of gridSelection?.rows) {
|
||||||
// if (selectedRow === row) {
|
// if (scrollToRowKey === row) {
|
||||||
// return {
|
// return {
|
||||||
// bgCell: rowColor.bgCell,
|
// bgCell: rowColor.bgCell,
|
||||||
// bgCellMedium: rowColor.bgCellMedium
|
// bgCellMedium: rowColor.bgCellMedium
|
||||||
|
|||||||
@@ -2,5 +2,9 @@ export {GlidlerAPIAdaptorForGoLangv2 } from './components/adaptors/GlidlerAPIAda
|
|||||||
export {GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor'
|
export {GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor'
|
||||||
export {GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor'
|
export {GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor'
|
||||||
export * from './components/Column'
|
export * from './components/Column'
|
||||||
export {useGridlerStore } from './components/GridlerStore'
|
export {type GridlerProps,type GridlerRef,type GridlerState, useGridlerStore } from './components/GridlerStore'
|
||||||
|
export { GridlerRightMenuIcon } from './components/RightMenuIcon'
|
||||||
export {Gridler} from './Gridler'
|
export {Gridler} from './Gridler'
|
||||||
|
export {GoAPIHeaders} from './utils'
|
||||||
|
export type {FetchAPIOperation} from './utils'
|
||||||
|
export type {APIOptions} from './utils/types'
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
import { Button, Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
import { useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import type { GridlerColumns } from '../components/Column';
|
import type { GridlerColumns } from '../components/Column';
|
||||||
|
|
||||||
import { GlidlerAPIAdaptorForGoLangv2 } from '../components/adaptors';
|
import { GlidlerAPIAdaptorForGoLangv2 } from '../components/adaptors';
|
||||||
|
import { type GridlerRef } from '../components/GridlerStore';
|
||||||
import { Gridler } from '../Gridler';
|
import { Gridler } from '../Gridler';
|
||||||
|
|
||||||
export const GridlerGoAPIExampleEventlog = () => {
|
export const GridlerGoAPIExampleEventlog = () => {
|
||||||
@@ -12,9 +13,11 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
defaultValue: 'http://localhost:8080/api',
|
defaultValue: 'http://localhost:8080/api',
|
||||||
key: 'apiurl',
|
key: 'apiurl',
|
||||||
});
|
});
|
||||||
|
const ref = useRef<GridlerRef>(null);
|
||||||
const [apiKey, setApiKey] = useLocalStorage({ defaultValue: '', key: 'apikey' });
|
const [apiKey, setApiKey] = useLocalStorage({ defaultValue: '', key: 'apikey' });
|
||||||
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
||||||
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
const [sections, setSections] = useState<Record<string, unknown> | undefined>(undefined);
|
const [sections, setSections] = useState<Record<string, unknown> | undefined>(undefined);
|
||||||
const columns: GridlerColumns = [
|
const columns: GridlerColumns = [
|
||||||
{
|
{
|
||||||
@@ -106,14 +109,24 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
//console.log('GridlerGoAPIExampleEventlog onChange', v);
|
//console.log('GridlerGoAPIExampleEventlog onChange', v);
|
||||||
setValues(v);
|
setValues(v);
|
||||||
}}
|
}}
|
||||||
sections={sections}
|
ref={ref}
|
||||||
selectedRow={selectRow ? parseInt(selectRow, 10) : undefined}
|
scrollToRowKey={selectRow ? parseInt(selectRow, 10) : undefined}
|
||||||
|
searchStr={search}
|
||||||
|
sections={{ ...sections, rightElementDisabled: false }}
|
||||||
|
selectFirstRowOnMount={true}
|
||||||
selectMode="row"
|
selectMode="row"
|
||||||
|
title="Go API Example"
|
||||||
uniqueid="gridtest"
|
uniqueid="gridtest"
|
||||||
values={values}
|
values={values}
|
||||||
>
|
>
|
||||||
<GlidlerAPIAdaptorForGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
<GlidlerAPIAdaptorForGoLangv2
|
||||||
|
authtoken={apiKey}
|
||||||
|
options={[{ type: 'preload', value: 'PRO' }]}
|
||||||
|
//options={[{ type: 'fieldfilter', name: 'process', value: 'test' }]}
|
||||||
|
url={`${apiUrl}/public/process`}
|
||||||
|
/>
|
||||||
<Gridler.FormAdaptor
|
<Gridler.FormAdaptor
|
||||||
|
changeOnActiveClick={true}
|
||||||
descriptionField={'process'}
|
descriptionField={'process'}
|
||||||
onRequestForm={(request, data) => {
|
onRequestForm={(request, data) => {
|
||||||
console.log('Form requested', request, data);
|
console.log('Form requested', request, data);
|
||||||
@@ -122,6 +135,13 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
</Gridler>
|
</Gridler>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group>
|
<Group>
|
||||||
|
<TextInput
|
||||||
|
leftSection={<>S</>}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
placeholder="Search"
|
||||||
|
value={search}
|
||||||
|
w="190px"
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
onChange={(e) => setSelectRow(e.target.value)}
|
onChange={(e) => setSelectRow(e.target.value)}
|
||||||
placeholder="row"
|
placeholder="row"
|
||||||
@@ -136,6 +156,43 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
/>
|
/>
|
||||||
;
|
;
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
ref.current?.selectRow(20523);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select 20523
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
ref.current?.selectRow(4);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select 4
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
ref.current?.reloadRow(20523);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reload 20523
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
ref.current?.scrollToRow(16272);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Goto 2050
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//@ts-nocheck
|
||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
@@ -6,7 +7,12 @@ import { fn } from 'storybook/test';
|
|||||||
import { GridlerGoAPIExampleEventlog } from './Examples.goapi';
|
import { GridlerGoAPIExampleEventlog } from './Examples.goapi';
|
||||||
|
|
||||||
const Renderable = (props: any) => {
|
const Renderable = (props: any) => {
|
||||||
return <Box h="100%" mih="400px" miw="400px" w='100%' > <GridlerGoAPIExampleEventlog {...props} /></Box>;
|
return (
|
||||||
|
<Box h="100%" mih="400px" miw="400px" w="100%">
|
||||||
|
{' '}
|
||||||
|
<GridlerGoAPIExampleEventlog {...props} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
@@ -19,7 +25,7 @@ const meta = {
|
|||||||
component: Renderable,
|
component: Renderable,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||||
layout: 'centered',
|
//layout: 'centered',
|
||||||
},
|
},
|
||||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//@ts-nocheck
|
||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
@@ -24,7 +25,7 @@ const meta = {
|
|||||||
component: Renderable,
|
component: Renderable,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||||
layout: 'centered',
|
// layout: 'centered',
|
||||||
},
|
},
|
||||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
|
|||||||
270
src/Gridler/utils/golang-restapi-v2/index.ts
Normal file
270
src/Gridler/utils/golang-restapi-v2/index.ts
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
import {b64EncodeUnicode} from '@warkypublic/artemis-kit/base64'
|
||||||
|
const TOKEN_KEY = 'gridler_golang_restapi_v2_token'
|
||||||
|
|
||||||
|
export type APIOptionsType = {
|
||||||
|
autocreate?: boolean
|
||||||
|
autoref?: boolean
|
||||||
|
baseurl?: string
|
||||||
|
getAPIProvider?: () => { provider: string; providerKey: string }
|
||||||
|
getAuthToken?: () => string
|
||||||
|
operations?: Array<FetchAPIOperation>
|
||||||
|
postfix?: string
|
||||||
|
prefix?: string
|
||||||
|
requestTimeoutSec?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface APIResponse {
|
||||||
|
errmsg: string
|
||||||
|
payload?: any
|
||||||
|
retval: number
|
||||||
|
}
|
||||||
|
export interface FetchAPIOperation {
|
||||||
|
name?: string
|
||||||
|
op?: string
|
||||||
|
type: GoAPIHeaderTypes //x-fieldfilter
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description Types for the Go Rest API headers
|
||||||
|
* @typedef {String} GoAPIEnum
|
||||||
|
*/
|
||||||
|
export type GoAPIEnum =
|
||||||
|
| 'advsql'
|
||||||
|
| 'api-key'
|
||||||
|
| 'api-range-from'
|
||||||
|
| 'api-range-size'
|
||||||
|
| 'api-range-total'
|
||||||
|
| 'api-src'
|
||||||
|
| 'api'
|
||||||
|
| 'association_autocreate'
|
||||||
|
| 'association_autoupdate'
|
||||||
|
| 'association-update'
|
||||||
|
| 'cql-sel'
|
||||||
|
| 'cursor-backward'// For x cursor-backward header
|
||||||
|
| 'cursor-forward' // For x cursor-forward header
|
||||||
|
| 'custom-sql-join'
|
||||||
|
| 'custom-sql-or'
|
||||||
|
| 'custom-sql-w'
|
||||||
|
| 'detailapi'
|
||||||
|
| 'distinct'
|
||||||
|
| 'expand'
|
||||||
|
| 'fetch-rownumber'
|
||||||
|
| 'fieldfilter'
|
||||||
|
| 'fieldfilter'
|
||||||
|
| 'files' //For x files header
|
||||||
|
| 'func'
|
||||||
|
| 'limit'
|
||||||
|
| 'no-return'
|
||||||
|
| 'not-select-fields'
|
||||||
|
| 'offset'
|
||||||
|
| 'parm'
|
||||||
|
| 'pkrow'
|
||||||
|
| 'preload'
|
||||||
|
| 'searchand'
|
||||||
|
| 'searchfilter'
|
||||||
|
| 'searchfilter'
|
||||||
|
| 'searchop'
|
||||||
|
| 'searchop'
|
||||||
|
| 'searchor'
|
||||||
|
| 'select-fields'
|
||||||
|
| 'simpleapi'
|
||||||
|
| 'skipcache'
|
||||||
|
| 'skipcount'
|
||||||
|
| 'sort'
|
||||||
|
|
||||||
|
|
||||||
|
export type GoAPIHeaderKeys = `x-${GoAPIEnum}`
|
||||||
|
|
||||||
|
|
||||||
|
export type GoAPIHeaderTypes = GoAPIEnum & string
|
||||||
|
|
||||||
|
|
||||||
|
export interface GoAPIOperation {
|
||||||
|
name?: string
|
||||||
|
op?: string
|
||||||
|
type: GoAPIHeaderTypes //x-fieldfilter
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
export interface MetaData {
|
||||||
|
limit?: number
|
||||||
|
offset?: number
|
||||||
|
total?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an array of objects by encoding specific values and setting headers.
|
||||||
|
*
|
||||||
|
* @param {Array<FetchAPIOperation>} ops - The array of FetchAPIOperation objects to be built.
|
||||||
|
* @param {Headers} [headers] - Optional headers to be set.
|
||||||
|
* @return {Array<FetchAPIOperation>} - The built array of FetchAPIOperation objects.
|
||||||
|
*/
|
||||||
|
const buildGoAPIOperation = (
|
||||||
|
ops: Array<FetchAPIOperation>,
|
||||||
|
headers?: Headers
|
||||||
|
): Array<FetchAPIOperation> => {
|
||||||
|
const newops = [...ops.filter((i) => i !== undefined && i.type !== undefined)]
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < newops.length; i++) {
|
||||||
|
if (!newops[i].name || newops[i].name === '') {
|
||||||
|
newops[i].name = ''
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'files' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'advsql' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newops[i].type === 'custom-sql-or' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newops[i].type === 'custom-sql-join' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'not-select-fields' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'custom-sql-w' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'select-fields' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'cql-sel' && !newops[i].value.startsWith('__')) {
|
||||||
|
newops[i].value = `__${b64EncodeUnicode(newops[i].value)}__`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
if (!newops || newops.length === 0) {
|
||||||
|
headers.set(`x-limit`, '10')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newops[i].type === 'association_autoupdate') {
|
||||||
|
headers.set(`association_autoupdate`, newops[i].value ?? '1')
|
||||||
|
}
|
||||||
|
if (newops[i].type === 'association_autocreate') {
|
||||||
|
headers.set(`association_autocreate`, newops[i].value ?? '1')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
newops[i].type === 'searchop' ||
|
||||||
|
newops[i].type === 'searchor' ||
|
||||||
|
newops[i].type === 'searchand'
|
||||||
|
) {
|
||||||
|
headers.set(
|
||||||
|
encodeURIComponent(`x-${newops[i].type}-${newops[i].op}-${newops[i].name}`),
|
||||||
|
String(newops[i].value)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
headers.set(
|
||||||
|
encodeURIComponent(
|
||||||
|
`x-${newops[i].type}${newops[i].name && newops[i].name !== '' ? '-' + newops[i].name : ''}`
|
||||||
|
),
|
||||||
|
String(newops[i].value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newops
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the headers from an array of FetchAPIOperation objects and returns them as an object.
|
||||||
|
*
|
||||||
|
* @param {Array<FetchAPIOperation>} ops - The array of FetchAPIOperation objects.
|
||||||
|
* @return {{ [key: string]: string }} - The headers as an object with string keys and string values.
|
||||||
|
*/
|
||||||
|
const GoAPIHeaders = (
|
||||||
|
ops: Array<FetchAPIOperation>,
|
||||||
|
headers?: Headers
|
||||||
|
): { [key: string]: string } => {
|
||||||
|
const head = new Headers()
|
||||||
|
const headerlist: Record<string,string> = {}
|
||||||
|
|
||||||
|
const authToken = getAuthToken?.()
|
||||||
|
if (authToken && authToken !== '') {
|
||||||
|
|
||||||
|
head.set('Authorization', `Token ${authToken}`)
|
||||||
|
} else {
|
||||||
|
const token = getAuthToken()
|
||||||
|
if (token) {
|
||||||
|
head.set('Authorization', `Token ${token}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
headers.forEach((v, k) => {
|
||||||
|
head.set(k, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const distinctOperations: Array<FetchAPIOperation> = []
|
||||||
|
|
||||||
|
for (const value of ops?.filter((val) => !!val) ?? []) {
|
||||||
|
const index = distinctOperations.findIndex(
|
||||||
|
(searchValue) => searchValue.name === value.name && searchValue.type === value.type
|
||||||
|
)
|
||||||
|
if (index === -1) {
|
||||||
|
distinctOperations.push(value)
|
||||||
|
} else {
|
||||||
|
distinctOperations[index] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildGoAPIOperation(distinctOperations, head)
|
||||||
|
|
||||||
|
head?.forEach((v, k) => {
|
||||||
|
headerlist[k] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
for (const key of Object.keys(headerlist)) {
|
||||||
|
headers.set(key, headerlist[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headerlist
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbacks = {
|
||||||
|
getAuthToken: () => {
|
||||||
|
if (localStorage) {
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY)
|
||||||
|
if (token) {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the authentication token from local storage.
|
||||||
|
*
|
||||||
|
* @return {string | undefined} The authentication token if found, otherwise undefined
|
||||||
|
*/
|
||||||
|
const getAuthToken = () => callbacks?.getAuthToken?.()
|
||||||
|
|
||||||
|
const setAuthTokenCallback = (cb: ()=> string) => {
|
||||||
|
callbacks.getAuthToken = cb
|
||||||
|
return callbacks.getAuthToken
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token in the local storage.
|
||||||
|
*
|
||||||
|
* @param {string} token - The authentication token to be set.
|
||||||
|
*/
|
||||||
|
const setAuthToken = (token: string) => {
|
||||||
|
if (localStorage) {
|
||||||
|
localStorage.setItem(TOKEN_KEY, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {buildGoAPIOperation,getAuthToken,GoAPIHeaders,setAuthToken,setAuthTokenCallback}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
export type APIOptionsType = {
|
|
||||||
autocreate?: boolean
|
|
||||||
autoref?: boolean
|
|
||||||
baseurl?: string
|
|
||||||
getAPIProvider?: () => { provider: string; providerKey: string }
|
|
||||||
getAuthToken?: () => string
|
|
||||||
operations?: Array<FetchAPIOperation>
|
|
||||||
postfix?: string
|
|
||||||
prefix?: string
|
|
||||||
requestTimeoutSec?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface APIResponse {
|
|
||||||
errmsg: string
|
|
||||||
payload?: any
|
|
||||||
retval: number
|
|
||||||
}
|
|
||||||
export interface FetchAPIOperation {
|
|
||||||
name?: string
|
|
||||||
op?: string
|
|
||||||
type: FetchOpTypes //x-fieldfilter
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
export type FetchOpTypes = GoAPIEnum & string
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Types for the Go Rest API headers
|
|
||||||
* @typedef {String} GoAPIEnum
|
|
||||||
*/
|
|
||||||
export type GoAPIEnum = 'advsql'
|
|
||||||
| 'api-key'
|
|
||||||
| 'api-range-from'
|
|
||||||
| 'api-range-size'
|
|
||||||
| 'api-range-total'
|
|
||||||
| 'api-src'
|
|
||||||
| 'api'
|
|
||||||
| 'association_autocreate'
|
|
||||||
| 'association_autoupdate'
|
|
||||||
| 'association-update'
|
|
||||||
| 'cql-sel'
|
|
||||||
| 'custom-sql-join'
|
|
||||||
| 'custom-sql-or'
|
|
||||||
| 'custom-sql-w'
|
|
||||||
| 'detailapi'
|
|
||||||
| 'distinct'
|
|
||||||
| 'expand'
|
|
||||||
| 'fetch-rownumber'
|
|
||||||
| 'fieldfilter'
|
|
||||||
| 'fieldfilter'
|
|
||||||
| 'func'
|
|
||||||
| 'limit'
|
|
||||||
| 'no-return'
|
|
||||||
| 'not-select-fields'
|
|
||||||
| 'offset'
|
|
||||||
| 'parm'
|
|
||||||
| 'pkrow'
|
|
||||||
| 'preload'
|
|
||||||
| 'searchfilter'
|
|
||||||
| 'searchfilter'
|
|
||||||
| 'searchop'
|
|
||||||
| 'searchop'
|
|
||||||
| 'select-fields'
|
|
||||||
| 'simpleapi'
|
|
||||||
| 'skipcache'
|
|
||||||
| 'skipcount'
|
|
||||||
| 'sort'
|
|
||||||
|
|
||||||
|
|
||||||
export type GoAPIHeaderKeys = `x-${GoAPIEnum}`
|
|
||||||
|
|
||||||
|
|
||||||
export type MetaCallback = (data: MetaData) => void
|
|
||||||
|
|
||||||
export interface MetaData {
|
|
||||||
limit?: number
|
|
||||||
offset?: number
|
|
||||||
total?: number
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {type APIOptionsType,type FetchAPIOperation,GoAPIHeaders} from './golang-restapi-v2'
|
||||||
@@ -5,21 +5,20 @@ import { fn } from 'storybook/test';
|
|||||||
|
|
||||||
import { MantineBetterMenusProvider, useMantineBetterMenus } from './';
|
import { MantineBetterMenusProvider, useMantineBetterMenus } from './';
|
||||||
|
|
||||||
|
const Renderable = (props: Record<string, unknown>) => {
|
||||||
const Renderable = (props: Record<string,unknown>) => {
|
|
||||||
return (
|
return (
|
||||||
<MantineBetterMenusProvider providerID='test' {...props} >
|
<MantineBetterMenusProvider providerID="test" {...props}>
|
||||||
<Menu/>
|
<Menu />
|
||||||
</MantineBetterMenusProvider>
|
</MantineBetterMenusProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Menu = () => {
|
const Menu = () => {
|
||||||
const menus = useMantineBetterMenus();
|
const menus = useMantineBetterMenus();
|
||||||
//menus.setState("menus",[{id:"test",items:[{id:"1",label:"Test",onClick:()=>{console.log("Clicked")}}]}])
|
//menus.setState("menus",[{id:"test",items:[{id:"1",label:"Test",onClick:()=>{console.log("Clicked")}}]}])
|
||||||
|
|
||||||
return <Button onClick={()=> menus.show("test",{})}>Menu</Button>;
|
return <Button onClick={() => menus.show('test', {})}>Menu</Button>;
|
||||||
}
|
};
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||||
@@ -31,7 +30,7 @@ const meta = {
|
|||||||
component: Renderable,
|
component: Renderable,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||||
layout: 'centered',
|
//layout: 'centered',
|
||||||
},
|
},
|
||||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
@@ -44,8 +43,6 @@ type Story = StoryObj<typeof meta>;
|
|||||||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||||
export const BasicExample: Story = {
|
export const BasicExample: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
|
||||||
label: 'Test',
|
label: 'Test',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const MenuItemRenderer = ({ children, label, ...props }: MantineBetterMenuInstan
|
|||||||
props.onClick?.(e);
|
props.onClick?.(e);
|
||||||
if (props.onClickAsync) {
|
if (props.onClickAsync) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
props.onClickAsync().finally(() => setLoading(false));
|
props.onClickAsync(e).finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
@@ -120,7 +120,7 @@ const MenuItemRenderer = ({ children, label, ...props }: MantineBetterMenuInstan
|
|||||||
props.onClick?.(e);
|
props.onClick?.(e);
|
||||||
if (props.onClickAsync) {
|
if (props.onClickAsync) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
props.onClickAsync().finally(() => setLoading(false));
|
props.onClickAsync(e).finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface MantineBetterMenuInstanceItem extends Partial<MenuItemProps> {
|
|||||||
items?: Array<MantineBetterMenuInstanceItem>;
|
items?: Array<MantineBetterMenuInstanceItem>;
|
||||||
label?: string;
|
label?: string;
|
||||||
onClick?: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
onClick?: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
onClickAsync?: () => Promise<void>;
|
onClickAsync?: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => Promise<void>;
|
||||||
renderer?:
|
renderer?:
|
||||||
| ((props: MantineBetterMenuInstanceItem & Record<string, unknown>) => ReactNode)
|
| ((props: MantineBetterMenuInstanceItem & Record<string, unknown>) => ReactNode)
|
||||||
| ReactNode;
|
| ReactNode;
|
||||||
|
|||||||
Reference in New Issue
Block a user