@wipco/sdui 0.3.0 → 0.4.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/dist/{chunk-YJMD6MOE.js → chunk-FGRR3ROJ.js} +95 -2
- package/dist/index.d.ts +114 -4
- package/dist/index.js +142 -89
- package/dist/meta.d.ts +180 -2
- package/dist/meta.js +1607 -1
- package/dist/rich.js +18 -4
- package/package.json +1 -1
|
@@ -1,7 +1,100 @@
|
|
|
1
1
|
import { Icon } from './chunk-ZUCI34QY.js';
|
|
2
|
-
import { useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
2
|
+
import { createContext, useContext, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
|
+
// src/env.ts
|
|
6
|
+
var DEV = (() => {
|
|
7
|
+
try {
|
|
8
|
+
const meta = import.meta;
|
|
9
|
+
return Boolean(meta.env?.DEV);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
|
|
15
|
+
// src/adapter.ts
|
|
16
|
+
function isSafeNavigationTarget(to) {
|
|
17
|
+
if (typeof to !== "string") return false;
|
|
18
|
+
const value = to.replace(/[\u0000-\u0020]/g, "");
|
|
19
|
+
if (value === "") return false;
|
|
20
|
+
if (value.startsWith("//")) return false;
|
|
21
|
+
if (value.startsWith("/") || value.startsWith("?") || value.startsWith("#")) return true;
|
|
22
|
+
return /^https?:\/\//i.test(value);
|
|
23
|
+
}
|
|
24
|
+
async function defaultFetch(path, init) {
|
|
25
|
+
const response = await fetch(path, {
|
|
26
|
+
method: init.method,
|
|
27
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
28
|
+
credentials: "include",
|
|
29
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
|
|
30
|
+
signal: init.signal
|
|
31
|
+
});
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
let json = null;
|
|
34
|
+
if (text) {
|
|
35
|
+
try {
|
|
36
|
+
json = JSON.parse(text);
|
|
37
|
+
} catch {
|
|
38
|
+
json = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const message = json && typeof json === "object" && typeof json.message === "string" ? json.message : `\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`;
|
|
43
|
+
throw new Error(message);
|
|
44
|
+
}
|
|
45
|
+
return json;
|
|
46
|
+
}
|
|
47
|
+
var defaultAdapter = {
|
|
48
|
+
navigate: (to) => {
|
|
49
|
+
if (!isSafeNavigationTarget(to)) {
|
|
50
|
+
console.error(`[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 navigate \uB300\uC0C1: ${String(to)}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
window.location.assign(to);
|
|
54
|
+
},
|
|
55
|
+
fetch: defaultFetch,
|
|
56
|
+
rawFetch: (path) => fetch(path, { credentials: "include" }),
|
|
57
|
+
// 항등함수. 계약을 문서화하고, 컴포넌트가 `useSduiAssetResolver()`를 조건 없이
|
|
58
|
+
// 부를 수 있게 하는 것이 전부다. **이 함수 참조 자체가 표식**이기도 하다 —
|
|
59
|
+
// `assetResolverOf`가 이것과 같은지 보고 "주입 없음"을 판정한다.
|
|
60
|
+
resolveAsset: (url) => url,
|
|
61
|
+
invalidate: (prefix) => {
|
|
62
|
+
if (DEV) console.warn(`[sdui] invalidate \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428 (prefix: ${prefix})`);
|
|
63
|
+
},
|
|
64
|
+
refresh: () => {
|
|
65
|
+
if (DEV) console.warn("[sdui] refresh \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428");
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function resolveAdapter(adapter) {
|
|
69
|
+
const merged = { ...defaultAdapter };
|
|
70
|
+
if (adapter) {
|
|
71
|
+
for (const [key, value] of Object.entries(adapter)) {
|
|
72
|
+
if (value !== void 0) merged[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return merged;
|
|
76
|
+
}
|
|
77
|
+
var SduiAdapterContext = createContext(null);
|
|
78
|
+
function useSduiAdapter() {
|
|
79
|
+
return useContext(SduiAdapterContext);
|
|
80
|
+
}
|
|
81
|
+
function assetResolverOf(adapter) {
|
|
82
|
+
const resolve = adapter?.resolveAsset;
|
|
83
|
+
if (!resolve || resolve === defaultAdapter.resolveAsset) return null;
|
|
84
|
+
return resolve;
|
|
85
|
+
}
|
|
86
|
+
function useSduiAssetResolver() {
|
|
87
|
+
const adapter = useContext(SduiAdapterContext);
|
|
88
|
+
return adapter?.resolveAsset ?? defaultAdapter.resolveAsset;
|
|
89
|
+
}
|
|
90
|
+
function useSduiSandbox() {
|
|
91
|
+
return useContext(SduiAdapterContext)?.sandbox === true;
|
|
92
|
+
}
|
|
93
|
+
function hostFetchOf(adapter) {
|
|
94
|
+
const hostFetch = adapter?.fetch;
|
|
95
|
+
if (!hostFetch || hostFetch === defaultAdapter.fetch) return null;
|
|
96
|
+
return hostFetch;
|
|
97
|
+
}
|
|
5
98
|
function HoistedStyle({ id, css }) {
|
|
6
99
|
return /* @__PURE__ */ jsx("style", { href: id, precedence: "medium", children: css });
|
|
7
100
|
}
|
|
@@ -401,4 +494,4 @@ var DP_CSS = `
|
|
|
401
494
|
.wp-dp__day--range-start.wp-dp__day--today .wp-dp__day-btn,.wp-dp__day--range-end.wp-dp__day--today .wp-dp__day-btn{box-shadow:none}
|
|
402
495
|
`;
|
|
403
496
|
|
|
404
|
-
export { DatePicker, DateRangePicker, HoistedStyle };
|
|
497
|
+
export { DEV, DatePicker, DateRangePicker, HoistedStyle, SduiAdapterContext, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,10 +16,15 @@ interface SduiAdapter {
|
|
|
16
16
|
/**
|
|
17
17
|
* submitForm 등 JSON API 호출. 성공 시 응답 JSON을, 실패 시
|
|
18
18
|
* Error(사용자에게 보여줄 메시지)를 던진다.
|
|
19
|
+
*
|
|
20
|
+
* `signal`은 호출부가 중단할 수 있는 조회(목록 재조회 등)에서만 넘어온다 —
|
|
21
|
+
* 선택 사항이라 무시해도 계약은 성립하지만, 넘겨받은 호스트는 자기 클라이언트에
|
|
22
|
+
* 그대로 흘려 보내면 파라미터가 바뀔 때 이전 요청이 실제로 끊긴다.
|
|
19
23
|
*/
|
|
20
24
|
fetch?: (path: string, init: {
|
|
21
25
|
method: string;
|
|
22
26
|
body?: unknown;
|
|
27
|
+
signal?: AbortSignal;
|
|
23
28
|
}) => Promise<unknown>;
|
|
24
29
|
/** download 액션용 원시 fetch. 기본: fetch(path, { credentials: 'include' }) */
|
|
25
30
|
rawFetch?: (path: string) => Promise<Response>;
|
|
@@ -39,8 +44,100 @@ interface SduiAdapter {
|
|
|
39
44
|
* 어긋나지 않게 한다. 기본: window.history.replaceState.
|
|
40
45
|
*/
|
|
41
46
|
replaceQuery?: (params: URLSearchParams) => void;
|
|
47
|
+
/**
|
|
48
|
+
* 스펙이 준 **자산 URL**을 브라우저가 자동으로 받아 오기 직전에 가로채는 자리.
|
|
49
|
+
*
|
|
50
|
+
* `fetch`·`navigate`와 결정적으로 다르다. 저 둘은 어댑터가 **호출을 소유**하므로
|
|
51
|
+
* 구현을 비워 두면 아무 일도 안 일어난다. 자산은 그렇지 않다 — `<img src>`나
|
|
52
|
+
* `background-image: url(…)`이 DOM에 닿는 순간 요청을 내는 것은 **브라우저**이고,
|
|
53
|
+
* 어댑터는 그 자리에 없다. 그래서 값이 DOM에 닿기 전에 갈아 끼우는 훅이 필요하다.
|
|
54
|
+
*
|
|
55
|
+
* @param url 스펙에서 온 원본 URL(상대·절대 모두).
|
|
56
|
+
* @param ctx 어느 컴포넌트의 어느 자리에서 왔는지.
|
|
57
|
+
* @returns 실제로 쓸 URL. `null`이면 그 프롭(또는 CSS 선언 하나)을 통째로 뺀다.
|
|
58
|
+
*
|
|
59
|
+
* 기본 구현은 **항등함수**다(`defaultAdapter.resolveAsset`). 진짜 앱은 자기 세션으로
|
|
60
|
+
* 자기 서버의 이미지를 봐야 하므로 여기서 무엇이든 바꾸면 그게 곧 회귀다.
|
|
61
|
+
* 바꾸는 쪽은 편집기 캔버스 하나뿐이다 — 저장도 안 된 남의 스펙 텍스트를 편집자의
|
|
62
|
+
* 특권 세션으로, 사용자가 방문 의사를 밝힌 적 없이 그리는 유일한 자리라서다.
|
|
63
|
+
*/
|
|
64
|
+
resolveAsset?: SduiAssetResolver;
|
|
65
|
+
/**
|
|
66
|
+
* **이 화면은 진짜가 아니다** — 편집기 캔버스처럼 "저장도 안 된 남의 스펙을 편집자의
|
|
67
|
+
* 특권 세션으로 그리는" 자리라는 선언.
|
|
68
|
+
*
|
|
69
|
+
* 위의 어떤 필드로도 대신할 수 없어서 따로 둔다. `fetch`·`navigate`는 어댑터가
|
|
70
|
+
* **호출을 소유**하므로 막으면 그만이고, `resolveAsset`은 브라우저가 소유하는 자산
|
|
71
|
+
* 요청을 값 교체로 가로챈다. 그런데 세 번째 부류가 있다 — **컴포넌트가 자기 데이터
|
|
72
|
+
* 클라이언트로 직접 조회하는 경우**다. 소비 앱의 `RemoteSearchResults`는 TanStack
|
|
73
|
+
* Query로 조회하는데, 그 캐시는 모듈 싱글턴이라 어댑터가 끼어들 자리가 아예 없고,
|
|
74
|
+
* `queryFn`만 어댑터로 돌리면 캔버스가 돌려준 빈 응답이 **전역 캐시에 실려** 편집기를
|
|
75
|
+
* 닫은 뒤 진짜 페이지까지 오염시킨다. 그런 컴포넌트는 "요청을 보낼지 말지"를 스스로
|
|
76
|
+
* 정해야 하고, 그 판단 재료가 이 플래그다.
|
|
77
|
+
*
|
|
78
|
+
* 검토하고 버린 대용 신호 둘 — 둘 다 **"샌드박스"가 아닌 것을 샌드박스로 읽는다**:
|
|
79
|
+
* - `assetResolverOf(adapter) !== null`: CDN 리라이팅처럼 **정당하게** `resolveAsset`을
|
|
80
|
+
* 꽂은 호스트가 목록을 통째로 잃는다.
|
|
81
|
+
* - `resolveAsset('/__probe', …)`가 원본을 안 돌려주는지 되묻는 행동 프로브: 위와 같은
|
|
82
|
+
* 오판을 하면서 의도가 코드에 안 남는다.
|
|
83
|
+
*
|
|
84
|
+
* 기본값은 "선언 없음"(=진짜 앱)이다. 진짜 앱 어댑터에는 이 키가 아예 없으므로
|
|
85
|
+
* {@link useSduiSandbox}는 `false`를 돌려주고 **기존 동작이 한 글자도 안 바뀐다.**
|
|
86
|
+
* 켜는 곳은 편집기 캔버스 하나뿐이다.
|
|
87
|
+
*/
|
|
88
|
+
sandbox?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** {@link SduiAdapter.resolveAsset}가 받는 맥락 — 어디서 온 URL인지 판단 재료. */
|
|
91
|
+
interface SduiAssetContext {
|
|
92
|
+
/** 이 URL을 그리는 컴포넌트 이름(스펙의 `component`). */
|
|
93
|
+
component: string;
|
|
94
|
+
/** URL이 실린 프롭 이름. 중첩 값이면 그 자리의 키(`items[].src`면 `src`). */
|
|
95
|
+
prop: string;
|
|
96
|
+
/**
|
|
97
|
+
* 브라우저가 이 URL을 **어떤 경로로** 부르는가.
|
|
98
|
+
* - `attribute` — `<img src>` 같은 DOM URL 속성
|
|
99
|
+
* - `css` — 인라인 스타일 선언값 안의 `url()`
|
|
100
|
+
*
|
|
101
|
+
* 둘을 구분하는 이유: 되돌려 줄 수 있는 값이 다르다. 속성 자리에는 자리표시자 이미지를
|
|
102
|
+
* 넣어 박스를 유지할 수 있지만, `cursor: url(…)`에 이미지를 넣으면 CSS가 유효하지 않고
|
|
103
|
+
* (키워드 대체값이 필요하다) `mask-image`에 넣으면 요소 자체가 그 모양으로 깎인다.
|
|
104
|
+
*/
|
|
105
|
+
kind: 'attribute' | 'css';
|
|
106
|
+
/** `kind: 'css'`일 때 그 선언의 속성명. React 표기 그대로다(`backgroundImage`). */
|
|
107
|
+
cssProperty?: string;
|
|
42
108
|
}
|
|
43
|
-
|
|
109
|
+
type SduiAssetResolver = (url: string, ctx: SduiAssetContext) => string | null;
|
|
110
|
+
declare const defaultAdapter: Required<Pick<SduiAdapter, 'navigate' | 'fetch' | 'rawFetch' | 'resolveAsset'>> & SduiAdapter;
|
|
111
|
+
/**
|
|
112
|
+
* **스스로 URL을 만드는 컴포넌트**를 위한 자산 관문.
|
|
113
|
+
*
|
|
114
|
+
* 렌더러의 프롭 순회는 "스펙이 준 URL"만 본다. 그런데 실제 우회는 프롭이 없어도 난다 —
|
|
115
|
+
* 소비 앱의 `QRDisplay`는 `<img src={apiUrl('/qr?data=…')}>`처럼 URL을 **안에서 만들어**
|
|
116
|
+
* 붙이므로 프롭 검사에 걸릴 문자열이 애초에 없다. 동일 오리진이라 쿠키까지 실린다.
|
|
117
|
+
* 그런 컴포넌트는 이 훅으로 스스로 관문을 통과시켜야 한다.
|
|
118
|
+
*
|
|
119
|
+
* const resolveAsset = useSduiAssetResolver();
|
|
120
|
+
* const src = resolveAsset(apiUrl(`/qr?data=${value}`), { component: 'QRDisplay', prop: 'src', kind: 'attribute' });
|
|
121
|
+
* return src === null ? null : <img src={src} alt="" />;
|
|
122
|
+
*
|
|
123
|
+
* 렌더러 밖(드롭인 사용)이면 항등함수를 돌려준다 — 감싸지 않은 소비자의 동작은 바뀌지 않는다.
|
|
124
|
+
*/
|
|
125
|
+
declare function useSduiAssetResolver(): SduiAssetResolver;
|
|
126
|
+
/**
|
|
127
|
+
* **지금 그려지는 곳이 샌드박스(편집기 캔버스)인가.**
|
|
128
|
+
*
|
|
129
|
+
* 스스로 데이터를 조회하는 컴포넌트가 "요청을 아예 내지 않는다"를 고를 때 쓴다.
|
|
130
|
+
* {@link useSduiAssetResolver}와 나란한 자리지만 성질이 다르다 — 저쪽은 이미 만들어진
|
|
131
|
+
* URL을 **갈아 끼우는** 값 변환이고, 이쪽은 조회를 **시작조차 하지 않게** 하는 분기다.
|
|
132
|
+
* 자산은 값을 바꾸면 끝이지만 조회는 캐시·구독·재조회가 딸려 와서, 늦게 막으면
|
|
133
|
+
* 이미 늦는다(빈 응답이 공유 캐시에 남는다).
|
|
134
|
+
*
|
|
135
|
+
* 렌더러 밖(드롭인 사용)이면 `false`다 — 감싸지 않은 소비자의 동작은 바뀌지 않는다.
|
|
136
|
+
*
|
|
137
|
+
* const sandbox = useSduiSandbox();
|
|
138
|
+
* const { data } = useAppQuery(sandbox ? null : endpoint); // 조회 자체를 끈다
|
|
139
|
+
*/
|
|
140
|
+
declare function useSduiSandbox(): boolean;
|
|
44
141
|
|
|
45
142
|
/**
|
|
46
143
|
* 권한 게이트 — 통과하면 children, 실패하면 fallback 슬롯(없으면 아무 것도)을 렌더한다.
|
|
@@ -606,7 +703,17 @@ declare function useSduiDispatch(): SduiDispatch;
|
|
|
606
703
|
* core는 특정 HTTP 클라이언트에 묶이지 않는다 — ERP는 SWR(useErpQuery)로 조회했지만
|
|
607
704
|
* 라이브러리는 "endpoint + 파라미터 → { data, total }" 한 줄짜리 계약만 요구하고,
|
|
608
705
|
* 호스트가 자기 클라이언트(TanStack Query, SWR, 사내 fetch 래퍼…)를 끼워 넣는다.
|
|
609
|
-
*
|
|
706
|
+
*
|
|
707
|
+
* 조회 함수를 고르는 순서는 셋이다:
|
|
708
|
+
*
|
|
709
|
+
* 1. `SduiSearchDataProvider`로 **명시 주입**한 fetcher — 가장 세다. 조회를 통째로 소유하는
|
|
710
|
+
* 소비자(ERP RemoteSearchResults 등)의 뜻을 다른 무엇도 덮지 않는다.
|
|
711
|
+
* 2. 렌더러 어댑터의 `fetch` — 호스트가 자기 인증·에러 처리를 꽂아 둔 자리다. 여기를 거치면
|
|
712
|
+
* 소비 앱은 세션 규약을 그대로 태우고, 편집기 캔버스처럼 어댑터로 바깥을 막아 둔
|
|
713
|
+
* 자리에서는 목록 조회도 **함께 막힌다**. 예전에는 이 경로만 잠금 밖이라, 캔버스가
|
|
714
|
+
* 뜨는 것만으로 저장하지도 않은 스펙이 운영 API를 세션 쿠키와 함께 때렸다.
|
|
715
|
+
* 3. 둘 다 없으면 브라우저 fetch(어댑터를 안 준 SduiRenderer와 같은 정책) — 감싸지 않은
|
|
716
|
+
* 드롭인 소비자의 동작을 바꾸지 않기 위한 폴백이다.
|
|
610
717
|
*/
|
|
611
718
|
|
|
612
719
|
interface SearchRow {
|
|
@@ -636,6 +743,9 @@ interface RemotePageState {
|
|
|
636
743
|
/**
|
|
637
744
|
* endpoint+params 조회 훅. 라이브러리 안에 캐시를 두지 않는다(호스트 클라이언트의 몫) —
|
|
638
745
|
* 대신 직전 페이지를 유지해(keepPreviousData 상당) 페이지 이동 중 목록이 깜빡이지 않게 한다.
|
|
746
|
+
*
|
|
747
|
+
* 중단 신호는 fetcher에 넘기지만 호스트가 무시할 수도 있다(어댑터 계약상 선택 사항) —
|
|
748
|
+
* 그래서 늦게 도착한 응답은 아래 `alive` 검사로 한 번 더 버린다.
|
|
639
749
|
*/
|
|
640
750
|
declare function useRemoteSearchPage(endpoint: string, params: URLSearchParams): RemotePageState;
|
|
641
751
|
|
|
@@ -1318,7 +1428,7 @@ declare class NodeErrorBoundary extends React__default.Component<Props, State> {
|
|
|
1318
1428
|
static getDerivedStateFromError(error: Error): State;
|
|
1319
1429
|
componentDidCatch(error: Error): void;
|
|
1320
1430
|
componentDidUpdate(prevProps: Props): void;
|
|
1321
|
-
render(): string | number | bigint | boolean |
|
|
1431
|
+
render(): string | number | bigint | boolean | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | React__default.JSX.Element | null | undefined;
|
|
1322
1432
|
}
|
|
1323
1433
|
|
|
1324
|
-
export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type CustomActionHandler, type CustomValidator, DatePicker, DateRangePicker, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiBadgeCell, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, type SduiTextCell, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
|
|
1434
|
+
export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type CustomActionHandler, type CustomValidator, DatePicker, DateRangePicker, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, type SduiTextCell, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DateRangePicker, DatePicker, HoistedStyle } from './chunk-
|
|
2
|
-
export { DatePicker, DateRangePicker } from './chunk-
|
|
1
|
+
import { DEV, DateRangePicker, DatePicker, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, HoistedStyle, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-FGRR3ROJ.js';
|
|
2
|
+
export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-FGRR3ROJ.js';
|
|
3
3
|
import { searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-KADFN76X.js';
|
|
4
4
|
export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-KADFN76X.js';
|
|
5
5
|
import { Toast, Textarea, Tag, Tabs, Table, Switch, StatCard, Spinner, Select, ProgressBar, Pagination, Input, IconButton, Icon, Field, EmptyState, Checkbox, Card, Button, Badge, Alert, SegmentedControl, Dialog } from './chunk-ZUCI34QY.js';
|
|
@@ -7,72 +7,99 @@ export { Alert, Avatar, Badge, Breadcrumb, Button, Card, Checkbox, Dialog, Empty
|
|
|
7
7
|
import React, { createContext, useContext, useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
|
-
// src/
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
credentials: "include",
|
|
34
|
-
body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
|
|
35
|
-
});
|
|
36
|
-
const text = await response.text();
|
|
37
|
-
let json = null;
|
|
38
|
-
if (text) {
|
|
39
|
-
try {
|
|
40
|
-
json = JSON.parse(text);
|
|
41
|
-
} catch {
|
|
42
|
-
json = null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
const message = json && typeof json === "object" && typeof json.message === "string" ? json.message : `\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`;
|
|
47
|
-
throw new Error(message);
|
|
10
|
+
// src/asset-url.ts
|
|
11
|
+
var FETCHING_URL_PROPS = /* @__PURE__ */ new Set([
|
|
12
|
+
"src",
|
|
13
|
+
"srcSet",
|
|
14
|
+
"imageSrcSet",
|
|
15
|
+
"poster",
|
|
16
|
+
"data",
|
|
17
|
+
"background",
|
|
18
|
+
// SVG `<use href>`/`<image xlink:href>`는 실제로 문서를 받아 온다. 어휘에 `<use>`가
|
|
19
|
+
// 없어 오늘은 도달 불가지만, 성질상 이쪽이다.
|
|
20
|
+
"xlinkHref"
|
|
21
|
+
]);
|
|
22
|
+
var NAVIGATION_URL_PROPS = /* @__PURE__ */ new Set(["href", "action", "formAction"]);
|
|
23
|
+
var URL_PROPS = /* @__PURE__ */ new Set([
|
|
24
|
+
...FETCHING_URL_PROPS,
|
|
25
|
+
...NAVIGATION_URL_PROPS
|
|
26
|
+
]);
|
|
27
|
+
function isDangerousUrlValue(value) {
|
|
28
|
+
const normalized = value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
|
|
29
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(normalized);
|
|
30
|
+
if (!scheme) return false;
|
|
31
|
+
if (scheme[1] === "data") {
|
|
32
|
+
return !/^data:image\/(?!svg\+xml)[a-z0-9.+-]+[;,]/.test(normalized);
|
|
48
33
|
}
|
|
49
|
-
return
|
|
34
|
+
return !["http", "https", "mailto", "tel", "blob"].includes(scheme[1]);
|
|
50
35
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
36
|
+
function isStyleProp(name) {
|
|
37
|
+
return name === "style" || name.length > 5 && name.endsWith("Style");
|
|
38
|
+
}
|
|
39
|
+
var CSS_LOADING_VALUE = /(?:url|image-set)\s*\(/i;
|
|
40
|
+
var CSS_URL = /url\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|([^)'"]*))\s*\)/gi;
|
|
41
|
+
var CSS_BARE_IMAGE_SET = /image-set\(\s*["']/i;
|
|
42
|
+
function cssUrlLiteral(url) {
|
|
43
|
+
return `url("${url.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}")`;
|
|
44
|
+
}
|
|
45
|
+
function resolveStyleAssets(style, resolve, ctx) {
|
|
46
|
+
let out = null;
|
|
47
|
+
for (const [property, value] of Object.entries(style)) {
|
|
48
|
+
if (typeof value !== "string" || !CSS_LOADING_VALUE.test(value)) continue;
|
|
49
|
+
let dropped = false;
|
|
50
|
+
const rewritten = value.replace(CSS_URL, (whole, dq, sq, bare) => {
|
|
51
|
+
const url = (dq ?? sq ?? bare ?? "").trim();
|
|
52
|
+
if (url === "") return whole;
|
|
53
|
+
const next = resolve(url, { ...ctx, kind: "css", cssProperty: property });
|
|
54
|
+
if (next === null) {
|
|
55
|
+
dropped = true;
|
|
56
|
+
return whole;
|
|
57
|
+
}
|
|
58
|
+
return next === url ? whole : cssUrlLiteral(next);
|
|
59
|
+
});
|
|
60
|
+
const unsafeRemains = CSS_BARE_IMAGE_SET.test(rewritten);
|
|
61
|
+
if (!dropped && !unsafeRemains && rewritten === value) continue;
|
|
62
|
+
out ??= { ...style };
|
|
63
|
+
if (dropped || unsafeRemains) delete out[property];
|
|
64
|
+
else out[property] = rewritten;
|
|
66
65
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
return out ?? style;
|
|
67
|
+
}
|
|
68
|
+
var MAX_DEPTH = 8;
|
|
69
|
+
function resolveAssetsIn(propName, value, resolve, component) {
|
|
70
|
+
return walk(propName, value, 0);
|
|
71
|
+
function walk(key, node, depth) {
|
|
72
|
+
if (depth > MAX_DEPTH) return node;
|
|
73
|
+
if (typeof node === "string") {
|
|
74
|
+
if (!FETCHING_URL_PROPS.has(key) || node === "") return node;
|
|
75
|
+
const next = resolve(node, { component, prop: key, kind: "attribute" });
|
|
76
|
+
return next === null ? void 0 : next;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(node)) {
|
|
79
|
+
let changed = false;
|
|
80
|
+
const next = node.map((entry) => {
|
|
81
|
+
const resolved = walk(key, entry, depth + 1);
|
|
82
|
+
if (resolved !== entry) changed = true;
|
|
83
|
+
return resolved;
|
|
84
|
+
});
|
|
85
|
+
return changed ? next : node;
|
|
73
86
|
}
|
|
87
|
+
if (typeof node !== "object" || node === null) return node;
|
|
88
|
+
if (Object.getPrototypeOf(node) !== Object.prototype && Object.getPrototypeOf(node) !== null) {
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
const record = node;
|
|
92
|
+
if (isStyleProp(key)) return resolveStyleAssets(record, resolve, { component, prop: key });
|
|
93
|
+
let out = null;
|
|
94
|
+
for (const [childKey, childValue] of Object.entries(record)) {
|
|
95
|
+
const resolved = walk(childKey, childValue, depth + 1);
|
|
96
|
+
if (resolved === childValue) continue;
|
|
97
|
+
out ??= { ...record };
|
|
98
|
+
if (resolved === void 0) delete out[childKey];
|
|
99
|
+
else out[childKey] = resolved;
|
|
100
|
+
}
|
|
101
|
+
return out ?? record;
|
|
74
102
|
}
|
|
75
|
-
return merged;
|
|
76
103
|
}
|
|
77
104
|
var SduiDispatchContext = createContext(null);
|
|
78
105
|
function useSduiDispatch() {
|
|
@@ -281,7 +308,7 @@ function cellRenderer(cell, key, dispatch) {
|
|
|
281
308
|
"a",
|
|
282
309
|
{
|
|
283
310
|
className: "wp-btn wp-btn--link",
|
|
284
|
-
href: to,
|
|
311
|
+
href: isSafeNavigationTarget(to) ? to : void 0,
|
|
285
312
|
onClick: (e) => {
|
|
286
313
|
e.preventDefault();
|
|
287
314
|
e.stopPropagation();
|
|
@@ -337,10 +364,16 @@ function cellRenderer(cell, key, dispatch) {
|
|
|
337
364
|
return void 0;
|
|
338
365
|
}
|
|
339
366
|
}
|
|
340
|
-
|
|
367
|
+
function searchUrl(endpoint, params) {
|
|
341
368
|
const query = params.toString();
|
|
342
|
-
|
|
343
|
-
|
|
369
|
+
return `${endpoint}${query ? endpoint.includes("?") ? "&" : "?" : ""}${query}`;
|
|
370
|
+
}
|
|
371
|
+
function toPage(body) {
|
|
372
|
+
const rows = Array.isArray(body?.data) ? body.data : [];
|
|
373
|
+
return { data: rows, total: typeof body?.total === "number" ? body.total : rows.length };
|
|
374
|
+
}
|
|
375
|
+
var defaultFetcher = async (endpoint, params, signal) => {
|
|
376
|
+
const response = await fetch(searchUrl(endpoint, params), {
|
|
344
377
|
headers: { accept: "application/json" },
|
|
345
378
|
credentials: "include",
|
|
346
379
|
signal
|
|
@@ -349,9 +382,14 @@ var defaultFetcher = async (endpoint, params, signal) => {
|
|
|
349
382
|
if (!response.ok) {
|
|
350
383
|
throw new Error(body?.message ?? `\uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`);
|
|
351
384
|
}
|
|
352
|
-
|
|
353
|
-
return { data: rows, total: typeof body?.total === "number" ? body.total : rows.length };
|
|
385
|
+
return toPage(body);
|
|
354
386
|
};
|
|
387
|
+
function adapterFetcher(hostFetch) {
|
|
388
|
+
return async (endpoint, params, signal) => {
|
|
389
|
+
const body = await hostFetch(searchUrl(endpoint, params), { method: "GET", signal });
|
|
390
|
+
return toPage(body);
|
|
391
|
+
};
|
|
392
|
+
}
|
|
355
393
|
var SearchDataContext = createContext(null);
|
|
356
394
|
function SduiSearchDataProvider({
|
|
357
395
|
fetcher,
|
|
@@ -360,7 +398,13 @@ function SduiSearchDataProvider({
|
|
|
360
398
|
return /* @__PURE__ */ jsx(SearchDataContext.Provider, { value: fetcher, children });
|
|
361
399
|
}
|
|
362
400
|
function useSearchFetcher() {
|
|
363
|
-
|
|
401
|
+
const injected = useContext(SearchDataContext);
|
|
402
|
+
const adapter = useSduiAdapter();
|
|
403
|
+
return useMemo(() => {
|
|
404
|
+
if (injected) return injected;
|
|
405
|
+
const hostFetch = hostFetchOf(adapter);
|
|
406
|
+
return hostFetch ? adapterFetcher(hostFetch) : defaultFetcher;
|
|
407
|
+
}, [injected, adapter]);
|
|
364
408
|
}
|
|
365
409
|
var EMPTY_PAGE = { data: [], total: 0 };
|
|
366
410
|
function useRemoteSearchPage(endpoint, params) {
|
|
@@ -1388,6 +1432,11 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
|
|
|
1388
1432
|
function isPlainObject(v) {
|
|
1389
1433
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1390
1434
|
}
|
|
1435
|
+
function checkUrlValue(key, value, path, issues) {
|
|
1436
|
+
if (typeof value !== "string" || !URL_PROPS.has(key)) return;
|
|
1437
|
+
if (!isDangerousUrlValue(value)) return;
|
|
1438
|
+
issues.push({ path, message: `${key}\uC5D0 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 URL \uC2A4\uD0B4\uC785\uB2C8\uB2E4: ${value}` });
|
|
1439
|
+
}
|
|
1391
1440
|
function validateSpec(page, knownComponents) {
|
|
1392
1441
|
const issues = [];
|
|
1393
1442
|
if (!isPlainObject(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
|
|
@@ -1426,6 +1475,7 @@ function walkChild(child, path, issues, known) {
|
|
|
1426
1475
|
});
|
|
1427
1476
|
continue;
|
|
1428
1477
|
}
|
|
1478
|
+
checkUrlValue(name, value, `${path}.props.${name}`, issues);
|
|
1429
1479
|
walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
|
|
1430
1480
|
}
|
|
1431
1481
|
if (node.component === "Form" && isPlainObject(node.props) && node.props.rules !== void 0) {
|
|
@@ -1513,6 +1563,7 @@ function walkValue(value, path, issues, actionEligible) {
|
|
|
1513
1563
|
}
|
|
1514
1564
|
for (const [k, v] of Object.entries(value)) {
|
|
1515
1565
|
if (k === "$calc" || k === "action") continue;
|
|
1566
|
+
checkUrlValue(k, v, `${path}.${k}`, issues);
|
|
1516
1567
|
walkValue(v, `${path}.${k}`, issues, k === "run" || k === "rowClick" || k.startsWith("on"));
|
|
1517
1568
|
}
|
|
1518
1569
|
}
|
|
@@ -1648,13 +1699,6 @@ var DENIED_PROPS2 = /* @__PURE__ */ new Set([
|
|
|
1648
1699
|
"constructor",
|
|
1649
1700
|
"prototype"
|
|
1650
1701
|
]);
|
|
1651
|
-
var URL_PROPS = /* @__PURE__ */ new Set(["href", "src", "action", "formAction", "xlinkHref", "poster"]);
|
|
1652
|
-
function isDangerousUrlValue(value) {
|
|
1653
|
-
const normalized = value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
|
|
1654
|
-
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(normalized);
|
|
1655
|
-
if (!scheme) return false;
|
|
1656
|
-
return !["http", "https", "mailto", "tel", "blob"].includes(scheme[1]);
|
|
1657
|
-
}
|
|
1658
1702
|
function childrenOf(node) {
|
|
1659
1703
|
if (node.children === void 0) return void 0;
|
|
1660
1704
|
if (Array.isArray(node.children)) return node.children;
|
|
@@ -1677,7 +1721,7 @@ function SduiNodeRenderer({
|
|
|
1677
1721
|
scope,
|
|
1678
1722
|
engine
|
|
1679
1723
|
}) {
|
|
1680
|
-
const { registry, resolveValue, dispatch } = engine;
|
|
1724
|
+
const { registry, resolveValue, dispatch, resolveAsset } = engine;
|
|
1681
1725
|
const slots = isPlainObject2(node.slots) ? node.slots : void 0;
|
|
1682
1726
|
const childList = childrenOf(node);
|
|
1683
1727
|
if (node.component === "ForEach") {
|
|
@@ -1728,6 +1772,11 @@ function SduiNodeRenderer({
|
|
|
1728
1772
|
warnOnce2(`url:${name}:${resolved}`, `[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ${name} \uAC12: ${resolved} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
|
|
1729
1773
|
continue;
|
|
1730
1774
|
}
|
|
1775
|
+
if (resolveAsset) {
|
|
1776
|
+
const gated = resolveAssetsIn(name, resolved, resolveAsset, String(node.component));
|
|
1777
|
+
if (gated === void 0) continue;
|
|
1778
|
+
resolved = gated;
|
|
1779
|
+
}
|
|
1731
1780
|
props[name] = resolved;
|
|
1732
1781
|
}
|
|
1733
1782
|
}
|
|
@@ -2110,8 +2159,8 @@ function SduiRenderer({
|
|
|
2110
2159
|
);
|
|
2111
2160
|
dispatchRef.current = dispatch;
|
|
2112
2161
|
const engine = useMemo(
|
|
2113
|
-
() => ({ registry, resolveValue, dispatch }),
|
|
2114
|
-
[registry, resolveValue, dispatch]
|
|
2162
|
+
() => ({ registry, resolveValue, dispatch, resolveAsset: assetResolverOf(host) }),
|
|
2163
|
+
[registry, resolveValue, dispatch, host]
|
|
2115
2164
|
);
|
|
2116
2165
|
const runtime = useMemo(
|
|
2117
2166
|
() => ({
|
|
@@ -2126,14 +2175,18 @@ function SduiRenderer({
|
|
|
2126
2175
|
}),
|
|
2127
2176
|
[state, setEntries]
|
|
2128
2177
|
);
|
|
2129
|
-
return
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2178
|
+
return (
|
|
2179
|
+
// 해석된 어댑터를 트리에 흘려보낸다 — dispatch를 거치지 않고 스스로 요청을 내는
|
|
2180
|
+
// 컴포넌트(목록 조회 등)도 호스트가 건 잠금 안에서 움직이게 하기 위해서다(adapter.ts 주석).
|
|
2181
|
+
/* @__PURE__ */ jsx(SduiAdapterContext.Provider, { value: host, children: /* @__PURE__ */ jsx(SduiDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsx(SduiRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsx(SduiPermissionProvider, { can, children: /* @__PURE__ */ jsxs("div", { className: "sdui-root", children: [
|
|
2182
|
+
page.title || page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head", children: /* @__PURE__ */ jsxs("div", { className: "wp-page-head__text", children: [
|
|
2183
|
+
page.title ? /* @__PURE__ */ jsx("h1", { className: "wp-page-head__title", children: page.title }) : null,
|
|
2184
|
+
page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head__sub", children: page.subtitle }) : null
|
|
2185
|
+
] }) }) : null,
|
|
2186
|
+
/* @__PURE__ */ jsx("div", { className: "wp-col", children: Array.isArray(page.body) ? page.body.map((child, i) => renderChild(child, i, EMPTY_SCOPE, engine)) : /* @__PURE__ */ jsx(SpecErrorCard, { children: "[sdui] page.body\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" }) }),
|
|
2187
|
+
toasts.length > 0 ? /* @__PURE__ */ jsx("div", { className: "wp-toast-region", children: toasts.map((t) => /* @__PURE__ */ jsx(Toast, { tone: t.tone, title: t.title, onClose: () => closeToast(t.id), children: t.message }, t.id)) }) : null
|
|
2188
|
+
] }) }) }) }) })
|
|
2189
|
+
);
|
|
2137
2190
|
}
|
|
2138
2191
|
|
|
2139
|
-
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, Spacer, Text, cellRenderer, createRegistry,
|
|
2192
|
+
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, normalizePageSizes, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
|