@iyulab/enterprise 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +28 -2
- package/dist/FormRow.d.ts +16 -0
- package/dist/data/ODataService.d.ts +17 -1
- package/dist/index.js +55 -9
- package/package.json +2 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`FormRow`'s grid tracks are now `minmax(0, 1fr)` rather than `1fr`, so columns declared
|
|
8
|
+
equal actually render equal.** A grid item's default `min-width: auto` refuses to shrink below
|
|
9
|
+
its content's min-content width, so one long value in a row was enough to blow out its own
|
|
10
|
+
column and squeeze the rest. Measured in a 400px container with one long unbreakable string:
|
|
11
|
+
a two-column row laid out at **425px / 8px** and a three-column row at **425px / 8px / 8px**,
|
|
12
|
+
and the row overflowed its container. With `minmax(0, 1fr)` the same cases lay out at
|
|
13
|
+
**196 / 196** and **128 / 128 / 128**. Nothing about the API changes, and rows whose content
|
|
14
|
+
already fit are unaffected.
|
|
15
|
+
- Long unbreakable content still overflows its own column — that is a separate axis, handled
|
|
16
|
+
on the cell with `overflow-wrap` (this package's peer elements already set it).
|
|
17
|
+
|
|
18
|
+
- **`@iyulab/components` is no longer an optional peer dependency.** The main entry imports it
|
|
19
|
+
unconditionally, so declaring it optional meant a consumer installing this package alone got
|
|
20
|
+
nothing installed and no warning — the failure surfaced only as a module-not-found at
|
|
21
|
+
runtime. Consumers already installing it are unaffected.
|
|
22
|
+
|
|
23
|
+
## 0.12.0
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`ApiError.details`** — OData v4 error envelopes carry per-field validation
|
|
28
|
+
errors in `error.details`, and `ODataService` already parsed that envelope to
|
|
29
|
+
build `ApiError.message`, but discarded the rest of the same parse. `ApiError`
|
|
30
|
+
now also carries `details` (`ApiErrorDetail[] | undefined`), so a caller can
|
|
31
|
+
bind server-side validation failures to the fields that produced them instead
|
|
32
|
+
of showing one flattened string. Backward compatible: callers that never read
|
|
33
|
+
`.details` are unaffected.
|
|
34
|
+
- Per the OData JSON Format v4.0 spec each detail entry MUST have `code` and
|
|
35
|
+
`message` and MAY have `target`, so `target` is optional on `ApiErrorDetail`
|
|
36
|
+
and entries that lack a string `code`/`message` are dropped; when nothing
|
|
37
|
+
usable remains `details` is `undefined` rather than an empty array.
|
|
38
|
+
- **`formatError(info)` now receives `details`** — the callback that shapes the
|
|
39
|
+
user-facing message previously got only `rawMessage` and the raw `body`, so an
|
|
40
|
+
app wanting to summarize per-field failures had to re-dig and re-validate the
|
|
41
|
+
same `error.details` the service had just parsed. `info` now carries the
|
|
42
|
+
validated array alongside the derived `rawMessage` it already had. Purely
|
|
43
|
+
additive to a callback input: existing implementations ignore the new field.
|
|
44
|
+
|
|
3
45
|
## 0.11.1
|
|
4
46
|
|
|
5
47
|
### Fixed
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npm install @iyulab/enterprise
|
|
|
18
18
|
| `FormRow` | React | 2컬럼 그리드 폼 행(`full`로 1컬럼) |
|
|
19
19
|
| `ApiConfig` | class | baseUrl/OData·API prefix·dev 판별 중앙 설정 |
|
|
20
20
|
| `createODataService` | factory | OData v4 + custom REST CRUD 서비스(401·토스트·에러파싱) |
|
|
21
|
-
| `ApiError` | class | HTTP status 를 실은 API 호출 실패 에러 |
|
|
21
|
+
| `ApiError` | class | HTTP status + OData `error.details`(필드별 검증 상세)를 실은 API 호출 실패 에러 |
|
|
22
22
|
| `createAuthClient` | factory | 쿠키 세션 인증(fetchMe/login/logout) — 제네릭 user/자격증명 |
|
|
23
23
|
| `createPermissionStore` · `hasPermission` 외 | store | 권한 스냅샷 store + 판정 free 함수 |
|
|
24
24
|
| `CurrencyHelper` | class | 통화 포맷(`formatKRW` 등) |
|
|
@@ -51,6 +51,8 @@ import { FormSection, FormRow } from '@iyulab/enterprise'
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
- `FormRow`는 기본 2컬럼 그리드입니다. 한 칸을 차지하려면 `full`을, 다른 열 수가 필요하면 `columns`를 씁니다.
|
|
54
|
+
- **열 폭 — 선언한 열은 실제로 균등합니다.** 트랙은 `1fr`이 아니라 `minmax(0, 1fr)`입니다. 그리드 아이템의 기본 `min-width: auto`는 내용의 min-content 아래로 줄어들기를 거부해서, 순수 `1fr`로 두면 긴 내용이 든 칸만 부풀고 나머지가 찌그러집니다(400px 컨테이너 실측: 2열 **425/8**, 3열 **425/8/8**). `minmax(0, 1fr)`은 트랙 최소를 0으로 만들어 같은 경우를 **196/196**·**128/128/128**로 만듭니다.
|
|
55
|
+
- ⚠**긴 불가분 문자열 자체의 넘침은 별개 축입니다.** 칸이 균등해져도 그 안의 긴 문자열은 자기 칸을 넘습니다 — 셀 쪽에서 `overflow-wrap`으로 다룹니다(`@iyulab/components`의 엘리먼트는 이미 그 값을 갖습니다).
|
|
54
56
|
- 두 컴포넌트 모두 `className`·`style`을 받아 기본값 **뒤에** 병합합니다 — 블록을 복제하지 않고 조정하는 정규 경로입니다. `FormSection`은 제목 줄만 바꾸는 `titleStyle`도 받습니다.
|
|
55
57
|
|
|
56
58
|
```tsx
|
|
@@ -121,10 +123,34 @@ await svc.apiPost<Order>('orders/7/attachments', form)
|
|
|
121
123
|
| `onUnauthorized(status)` | 401 시 호출 — 리다이렉트/재진입 가드는 앱이 처리 |
|
|
122
124
|
| `notify.success/error` | 토스트 훅 (생략 시 토스트 없음 — 순수) |
|
|
123
125
|
| `messages` | 사용자 대면 문구 (기본 영어, 지정 키만 대체) |
|
|
124
|
-
| `formatError(info)` | 에러 메시지 포매팅 오버라이드 (앱별 정책) |
|
|
126
|
+
| `formatError(info)` | 에러 메시지 포매팅 오버라이드 (앱별 정책) — `info` 는 `status`/`statusText`/`rawMessage`/`details`(검증된 `error.details`)/`body` 를 받는다 |
|
|
125
127
|
|
|
126
128
|
> 도메인 액션(상태 전이 등)·엔티티 목록·권한 코드는 라이브러리에 넣지 말고 앱 adapter 에 둔다.
|
|
127
129
|
|
|
130
|
+
#### 실패 응답 — `ApiError`
|
|
131
|
+
|
|
132
|
+
모든 메서드는 실패 시 `ApiError`(`Error` 상속)를 던진다. `status` 로 상태별 분기하고,
|
|
133
|
+
서버가 OData v4 오류 봉투에 필드별 검증 상세(`error.details`)를 실어 보내면 `details` 로
|
|
134
|
+
읽어 폼의 필드별 오류 표시에 바로 연결할 수 있다.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
try {
|
|
138
|
+
await svc.odataPost('Roles', draft)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
if (e instanceof ApiError && e.details) {
|
|
141
|
+
// [{ code: 'ValidationError', message: 'The Name field is required.', target: 'Name' }, …]
|
|
142
|
+
for (const d of e.details) markFieldError(d.target, d.message)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| 필드 | 설명 |
|
|
148
|
+
|------|------|
|
|
149
|
+
| `message` | 사용자 대면 메시지 (`formatError` → 서버 raw → status 폴백 순으로 결정) |
|
|
150
|
+
| `status` | HTTP status |
|
|
151
|
+
| `details` | `error.details` 항목 배열 — 규격상 `code`/`message` 는 필수, `target`(속성 이름)은 선택. 상세가 없거나 규격 형태가 아니면 `undefined` |
|
|
152
|
+
|
|
153
|
+
|
|
128
154
|
### 인증 + 권한 (`createAuthClient` · 권한 store)
|
|
129
155
|
|
|
130
156
|
쿠키 세션 인증 흐름과 권한 스냅샷을 승격. 사용자·자격증명 **형태는 앱이 제네릭으로 정의**하고, 권한 코드는 불투명 문자열로만 다룬다. `getPermissions` 를 주면 로그인/세션 조회 성공 시 권한 store 가 자동 갱신된다.
|
package/dist/FormRow.d.ts
CHANGED
|
@@ -5,6 +5,22 @@ import { CSSProperties, ReactNode } from 'react';
|
|
|
5
5
|
* 오버라이드 계약(docs/lob-layers.md §2)에 따라 `className`·`style` 을 받아 병합한다.
|
|
6
6
|
* `columns` 는 2열 고정이 맞지 않는 소비자가 **컴포넌트를 복제하지 않고** 조정하는 경로다
|
|
7
7
|
* — 복제가 시작되면 이 계층은 쓰기 전보다 나쁜 상태를 만든다.
|
|
8
|
+
*
|
|
9
|
+
* 🔴**트랙은 `minmax(0, 1fr)` 이지 `1fr` 이 아니다 — 순수 `1fr` 은 «균등» 을 약속하고**
|
|
10
|
+
* **지키지 않는다.** 그리드 아이템의 기본 `min-width: auto` 는 내용의 min-content 아래로
|
|
11
|
+
* 줄어들기를 거부하므로, 한 칸에 긴 내용이 들어오면 그 칸만 부풀고 나머지가 찌그러진다.
|
|
12
|
+
* 실측(400px 컨테이너 · 한 칸에 긴 불가분 문자열): 2열이 **425/8**, 3열이 **425/8/8** 이었고
|
|
13
|
+
* 행 자체가 컨테이너를 넘쳤다. `minmax(0, 1fr)` 은 트랙의 최소를 0 으로 만들어 **자식을**
|
|
14
|
+
* **건드리지 않고** 이를 고친다 — 같은 실측에서 **196/196** · **128/128/128**.
|
|
15
|
+
*
|
|
16
|
+
* ⚠**긴 불가분 내용 자체의 넘침은 별개 축이고 이 컴포넌트의 몫이 아니다.** 칸이 균등해져도
|
|
17
|
+
* 그 안의 긴 문자열은 여전히 자기 칸을 넘는다 — 그것은 셀의 `overflow-wrap` 이 답이다
|
|
18
|
+
* (`@iyulab/components` 의 엘리먼트는 이미 그 값을 갖는다). 같은 실측에서 `overflow-wrap` 을
|
|
19
|
+
* 주면 행의 넘침이 0 이 됐다.
|
|
20
|
+
*
|
|
21
|
+
* 계약은 `tests/form-layout-contract.test.ts` 가 고정한다 — 다만 그 파일은 **«선언» 을 재지**
|
|
22
|
+
* **«배치» 를 재지 않는다**(이 패키지엔 레이아웃을 계산하는 테스트 자리가 없다). 위 수치는
|
|
23
|
+
* 형제 패키지의 브라우저 프로젝트를 계측기로 빌린 일회성 탐침으로 쟀다.
|
|
8
24
|
*/
|
|
9
25
|
export declare function FormRow({ children, full, columns, className, style, }: {
|
|
10
26
|
children: ReactNode;
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { HttpResponse } from '@iyulab/http-client';
|
|
2
|
+
/**
|
|
3
|
+
* OData v4 오류 봉투의 `error.details` 항목 — 필드별 검증 실패 상세.
|
|
4
|
+
*
|
|
5
|
+
* 규격(OData JSON Format v4.0)상 각 항목은 `code`/`message` 를 **반드시** 갖고
|
|
6
|
+
* `target`(오류가 난 속성 이름)은 **선택**이다 — 필수로 선언하면 target 을 생략한
|
|
7
|
+
* 서버 응답에서 타입이 거짓말을 하게 된다.
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiErrorDetail {
|
|
10
|
+
code: string;
|
|
11
|
+
message: string;
|
|
12
|
+
target?: string;
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* API 호출 실패 에러 — HTTP status 를 실어 호출부가 상태별 분기(예: 404 도메인 문구)를 할 수 있게 한다.
|
|
4
16
|
* `Error` 를 상속하므로 기존 `e instanceof Error`/`e.message` 소비처는 그대로 동작한다.
|
|
5
17
|
*/
|
|
6
18
|
export declare class ApiError extends Error {
|
|
7
19
|
readonly status: number;
|
|
8
|
-
|
|
20
|
+
/** OData v4 오류 봉투의 `error.details`(필드별 검증 상세) — 서버 응답에 없거나 파싱 실패면 undefined. */
|
|
21
|
+
readonly details?: ApiErrorDetail[];
|
|
22
|
+
constructor(message: string, status: number, details?: ApiErrorDetail[]);
|
|
9
23
|
}
|
|
10
24
|
/** 서비스가 토스트/에러에 쓰는 사용자 대면 문구. 기본값은 영어 — 앱이 로케일별로 오버라이드한다. */
|
|
11
25
|
export interface ODataServiceMessages {
|
|
@@ -49,6 +63,8 @@ export interface ODataServiceConfig {
|
|
|
49
63
|
status: number;
|
|
50
64
|
statusText: string;
|
|
51
65
|
rawMessage?: string;
|
|
66
|
+
/** OData v4 `error.details` — 검증을 마친 항목만 실린다(`ApiError.details` 와 같은 값). */
|
|
67
|
+
details?: ApiErrorDetail[];
|
|
52
68
|
body?: unknown;
|
|
53
69
|
}) => string | undefined;
|
|
54
70
|
}
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,22 @@ function FormSection({ title, children, className, style, titleStyle }) {
|
|
|
59
59
|
* 오버라이드 계약(docs/lob-layers.md §2)에 따라 `className`·`style` 을 받아 병합한다.
|
|
60
60
|
* `columns` 는 2열 고정이 맞지 않는 소비자가 **컴포넌트를 복제하지 않고** 조정하는 경로다
|
|
61
61
|
* — 복제가 시작되면 이 계층은 쓰기 전보다 나쁜 상태를 만든다.
|
|
62
|
+
*
|
|
63
|
+
* 🔴**트랙은 `minmax(0, 1fr)` 이지 `1fr` 이 아니다 — 순수 `1fr` 은 «균등» 을 약속하고**
|
|
64
|
+
* **지키지 않는다.** 그리드 아이템의 기본 `min-width: auto` 는 내용의 min-content 아래로
|
|
65
|
+
* 줄어들기를 거부하므로, 한 칸에 긴 내용이 들어오면 그 칸만 부풀고 나머지가 찌그러진다.
|
|
66
|
+
* 실측(400px 컨테이너 · 한 칸에 긴 불가분 문자열): 2열이 **425/8**, 3열이 **425/8/8** 이었고
|
|
67
|
+
* 행 자체가 컨테이너를 넘쳤다. `minmax(0, 1fr)` 은 트랙의 최소를 0 으로 만들어 **자식을**
|
|
68
|
+
* **건드리지 않고** 이를 고친다 — 같은 실측에서 **196/196** · **128/128/128**.
|
|
69
|
+
*
|
|
70
|
+
* ⚠**긴 불가분 내용 자체의 넘침은 별개 축이고 이 컴포넌트의 몫이 아니다.** 칸이 균등해져도
|
|
71
|
+
* 그 안의 긴 문자열은 여전히 자기 칸을 넘는다 — 그것은 셀의 `overflow-wrap` 이 답이다
|
|
72
|
+
* (`@iyulab/components` 의 엘리먼트는 이미 그 값을 갖는다). 같은 실측에서 `overflow-wrap` 을
|
|
73
|
+
* 주면 행의 넘침이 0 이 됐다.
|
|
74
|
+
*
|
|
75
|
+
* 계약은 `tests/form-layout-contract.test.ts` 가 고정한다 — 다만 그 파일은 **«선언» 을 재지**
|
|
76
|
+
* **«배치» 를 재지 않는다**(이 패키지엔 레이아웃을 계산하는 테스트 자리가 없다). 위 수치는
|
|
77
|
+
* 형제 패키지의 브라우저 프로젝트를 계측기로 빌린 일회성 탐침으로 쟀다.
|
|
62
78
|
*/
|
|
63
79
|
function FormRow({ children, full, columns = 2, className, style }) {
|
|
64
80
|
if (full) return /* @__PURE__ */ jsx("div", {
|
|
@@ -73,7 +89,7 @@ function FormRow({ children, full, columns = 2, className, style }) {
|
|
|
73
89
|
className,
|
|
74
90
|
style: {
|
|
75
91
|
display: "grid",
|
|
76
|
-
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
|
92
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
77
93
|
gap: "var(--u-space-sm, 8px)",
|
|
78
94
|
...style
|
|
79
95
|
},
|
|
@@ -613,14 +629,31 @@ var ApiConfig = class {
|
|
|
613
629
|
* const rows = await svc.odataGet<BankAccount>('BankAccounts', { $top: '20' })
|
|
614
630
|
*/
|
|
615
631
|
/**
|
|
632
|
+
* `error.details` 를 검증해 추출한다 — 규격이 요구하는 형태(`code`/`message` 둘 다 문자열)를
|
|
633
|
+
* 갖춘 항목만 남긴다. 이 파일이 `error.message` 에 이미 적용하는 규칙(`typeof === "string"` 을
|
|
634
|
+
* 확인한 뒤 사용)과 같은 이유다: 검증 없이 캐스팅하면 타입만 맞고 런타임에 호출부가 깨진다.
|
|
635
|
+
* 쓸 수 있는 항목이 하나도 없으면 undefined 로 정규화한다 — 빈 배열을 주면 호출부의
|
|
636
|
+
* `if (e.details)` 가 참이 되어 "상세가 있다"고 오해한다.
|
|
637
|
+
*/
|
|
638
|
+
function extractErrorDetails(raw) {
|
|
639
|
+
if (!Array.isArray(raw)) return void 0;
|
|
640
|
+
const details = raw.filter((d) => {
|
|
641
|
+
if (typeof d !== "object" || d === null) return false;
|
|
642
|
+
const rec = d;
|
|
643
|
+
return typeof rec.code === "string" && typeof rec.message === "string";
|
|
644
|
+
});
|
|
645
|
+
return details.length > 0 ? details : void 0;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
616
648
|
* API 호출 실패 에러 — HTTP status 를 실어 호출부가 상태별 분기(예: 404 도메인 문구)를 할 수 있게 한다.
|
|
617
649
|
* `Error` 를 상속하므로 기존 `e instanceof Error`/`e.message` 소비처는 그대로 동작한다.
|
|
618
650
|
*/
|
|
619
651
|
var ApiError = class extends Error {
|
|
620
|
-
constructor(message, status) {
|
|
652
|
+
constructor(message, status, details) {
|
|
621
653
|
super(message);
|
|
622
654
|
this.name = "ApiError";
|
|
623
655
|
this.status = status;
|
|
656
|
+
this.details = details;
|
|
624
657
|
}
|
|
625
658
|
};
|
|
626
659
|
var DEFAULT_MESSAGES = {
|
|
@@ -674,27 +707,39 @@ function createODataService(config) {
|
|
|
674
707
|
const clean = path.startsWith("/") ? path.slice(1) : path;
|
|
675
708
|
return `${baseUrl}/${apiPrefix}/${clean}`;
|
|
676
709
|
};
|
|
677
|
-
/** OData 응답에서 사용자 친화적 에러 메시지 추출. */
|
|
678
|
-
async function
|
|
710
|
+
/** OData 응답에서 사용자 친화적 에러 메시지 + 구조화된 필드별 상세를 추출. */
|
|
711
|
+
async function extractErrorInfo(res) {
|
|
679
712
|
let body;
|
|
680
713
|
try {
|
|
681
714
|
body = await res.json();
|
|
682
715
|
} catch {
|
|
683
716
|
body = void 0;
|
|
684
717
|
}
|
|
685
|
-
const
|
|
718
|
+
const errorObj = body?.error;
|
|
719
|
+
const rawVal = errorObj?.message ?? body?.message ?? body?.Message;
|
|
686
720
|
const rawMessage = typeof rawVal === "string" ? rawVal : void 0;
|
|
721
|
+
const details = extractErrorDetails(errorObj?.details);
|
|
687
722
|
if (config.formatError) {
|
|
688
723
|
const m = config.formatError({
|
|
689
724
|
status: res.status,
|
|
690
725
|
statusText: res.statusText,
|
|
691
726
|
rawMessage,
|
|
727
|
+
details,
|
|
692
728
|
body
|
|
693
729
|
});
|
|
694
|
-
if (m) return
|
|
730
|
+
if (m) return {
|
|
731
|
+
message: m,
|
|
732
|
+
details
|
|
733
|
+
};
|
|
695
734
|
}
|
|
696
|
-
if (rawMessage && rawMessage.length <= 200) return
|
|
697
|
-
|
|
735
|
+
if (rawMessage && rawMessage.length <= 200) return {
|
|
736
|
+
message: rawMessage,
|
|
737
|
+
details
|
|
738
|
+
};
|
|
739
|
+
return {
|
|
740
|
+
message: messages.http[res.status] ?? `${messages.requestFailed} (${res.status})`,
|
|
741
|
+
details
|
|
742
|
+
};
|
|
698
743
|
}
|
|
699
744
|
/** 에러 확인 후 throw. 401 은 onUnauthorized 통지 후 세션 만료 에러로 단락. */
|
|
700
745
|
async function throwIfError(res) {
|
|
@@ -703,7 +748,8 @@ function createODataService(config) {
|
|
|
703
748
|
config.onUnauthorized?.(401);
|
|
704
749
|
throw new ApiError(messages.sessionExpired, 401);
|
|
705
750
|
}
|
|
706
|
-
|
|
751
|
+
const { message, details } = await extractErrorInfo(res);
|
|
752
|
+
throw new ApiError(message, res.status, details);
|
|
707
753
|
}
|
|
708
754
|
async function odataGet(entity, params) {
|
|
709
755
|
const u = new URL(odataUrl(entity));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/enterprise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Enterprise utilities and components for iyulab framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"enterprise",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@iyulab/components": "^1.27.0",
|
|
59
59
|
"@types/node": "^26.1.1",
|
|
60
60
|
"@types/react": "^19.2.14",
|
|
61
|
-
"typescript": "^
|
|
61
|
+
"typescript": "^6.0.2",
|
|
62
62
|
"vite": "^8.1.4",
|
|
63
63
|
"vite-plugin-dts": "^5.0.3",
|
|
64
64
|
"vitest": "^4.1.10"
|
|
@@ -66,10 +66,5 @@
|
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": ">=18",
|
|
68
68
|
"@iyulab/components": ">=1.27.0"
|
|
69
|
-
},
|
|
70
|
-
"peerDependenciesMeta": {
|
|
71
|
-
"@iyulab/components": {
|
|
72
|
-
"optional": true
|
|
73
|
-
}
|
|
74
69
|
}
|
|
75
70
|
}
|