@wipco/sdui 0.21.0 → 0.22.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.
@@ -1,102 +1,9 @@
1
1
  import { useDismiss, usePopoverAnchor, usePortalTo, HoistedStyle, AnchoredPopoverStyles, anchorStyle, focusStaysInside } from './chunk-XKLUCUVA.js';
2
2
  import { Icon } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
- import { createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
4
+ import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
- // src/env.ts
8
- var DEV = (() => {
9
- try {
10
- const meta = import.meta;
11
- return Boolean(meta.env?.DEV);
12
- } catch {
13
- return false;
14
- }
15
- })();
16
-
17
- // src/adapter.ts
18
- function isSafeNavigationTarget(to) {
19
- if (typeof to !== "string") return false;
20
- const value = to.replace(/[\u0000-\u0020]/g, "");
21
- if (value === "") return false;
22
- if (value.startsWith("//")) return false;
23
- if (value.startsWith("/") || value.startsWith("?") || value.startsWith("#")) return true;
24
- return /^https?:\/\//i.test(value);
25
- }
26
- async function defaultFetch(path, init) {
27
- const response = await fetch(path, {
28
- method: init.method,
29
- headers: { "content-type": "application/json", accept: "application/json" },
30
- credentials: "include",
31
- body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
32
- signal: init.signal
33
- });
34
- const text = await response.text();
35
- let json = null;
36
- if (text) {
37
- try {
38
- json = JSON.parse(text);
39
- } catch {
40
- json = null;
41
- }
42
- }
43
- if (!response.ok) {
44
- const message = json && typeof json === "object" && typeof json.message === "string" ? json.message : `\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`;
45
- throw new Error(message);
46
- }
47
- return json;
48
- }
49
- var defaultAdapter = {
50
- navigate: (to) => {
51
- if (!isSafeNavigationTarget(to)) {
52
- console.error(`[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 navigate \uB300\uC0C1: ${String(to)}`);
53
- return;
54
- }
55
- window.location.assign(to);
56
- },
57
- fetch: defaultFetch,
58
- rawFetch: (path) => fetch(path, { credentials: "include" }),
59
- // 항등함수. 계약을 문서화하고, 컴포넌트가 `useSduiAssetResolver()`를 조건 없이
60
- // 부를 수 있게 하는 것이 전부다. **이 함수 참조 자체가 표식**이기도 하다 —
61
- // `assetResolverOf`가 이것과 같은지 보고 "주입 없음"을 판정한다.
62
- resolveAsset: (url) => url,
63
- invalidate: (prefix) => {
64
- if (DEV) console.warn(`[sdui] invalidate \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428 (prefix: ${prefix})`);
65
- },
66
- refresh: () => {
67
- if (DEV) console.warn("[sdui] refresh \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428");
68
- }
69
- };
70
- function resolveAdapter(adapter) {
71
- const merged = { ...defaultAdapter };
72
- if (adapter) {
73
- for (const [key, value] of Object.entries(adapter)) {
74
- if (value !== void 0) merged[key] = value;
75
- }
76
- }
77
- return merged;
78
- }
79
- var SduiAdapterContext = createContext(null);
80
- function useSduiAdapter() {
81
- return useContext(SduiAdapterContext);
82
- }
83
- function assetResolverOf(adapter) {
84
- const resolve = adapter?.resolveAsset;
85
- if (!resolve || resolve === defaultAdapter.resolveAsset) return null;
86
- return resolve;
87
- }
88
- function useSduiAssetResolver() {
89
- const adapter = useContext(SduiAdapterContext);
90
- return adapter?.resolveAsset ?? defaultAdapter.resolveAsset;
91
- }
92
- function useSduiSandbox() {
93
- return useContext(SduiAdapterContext)?.sandbox === true;
94
- }
95
- function hostFetchOf(adapter) {
96
- const hostFetch = adapter?.fetch;
97
- if (!hostFetch || hostFetch === defaultAdapter.fetch) return null;
98
- return hostFetch;
99
- }
100
7
  var HM_RE = /^\d{2}:\d{2}$/;
101
8
  var LIST_HEIGHT = 260;
102
9
  var MINUTES_PER_DAY = 24 * 60;
@@ -288,4 +195,4 @@ var TP_CSS = `
288
195
  `;
289
196
  var TimePicker = boxed(TimePickerImpl, "TimePicker");
290
197
 
291
- export { DEV, SduiAdapterContext, TimePicker, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
198
+ export { TimePicker };
@@ -0,0 +1,99 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ // src/adapter.ts
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
+ }
98
+
99
+ export { DEV, SduiAdapterContext, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
@@ -1,9 +1,24 @@
1
1
  import { includesLooseSearch } from './chunk-U7ZK7WYH.js';
2
+ import { useSduiAdapter, hostFetchOf } from './chunk-WGRB5UL4.js';
2
3
  import { Icon, Button, Input, Spinner } from './chunk-WIDDHR7L.js';
3
4
  import { boxed } from './chunk-SAN4IX6N.js';
4
- import { useId, useRef, useState, useEffect, useMemo } from 'react';
5
+ import { useId, useRef, useMemo, useState, useEffect } from 'react';
5
6
  import { jsxs, jsx } from 'react/jsx-runtime';
6
7
 
8
+ function endpointSearchFn(endpoint, searchParam, hostFetch) {
9
+ return async (keyword, signal) => {
10
+ const sep = endpoint.includes("?") ? "&" : "?";
11
+ const url = `${endpoint}${sep}${searchParam}=${encodeURIComponent(keyword)}`;
12
+ try {
13
+ return toAutocompleteOptions(await hostFetch(url, { method: "GET", signal }));
14
+ } catch (error) {
15
+ if (error?.status === 503) {
16
+ return { options: [], status: "offline" };
17
+ }
18
+ throw error;
19
+ }
20
+ };
21
+ }
7
22
  function isRecord(value) {
8
23
  return typeof value === "object" && value !== null && !Array.isArray(value);
9
24
  }
@@ -34,6 +49,8 @@ var statusRowStyle = {
34
49
  };
35
50
  function AutocompleteFieldImpl({
36
51
  search,
52
+ endpoint,
53
+ searchParam = "q",
37
54
  options,
38
55
  labelKey = "label",
39
56
  valueKey = "value",
@@ -52,9 +69,17 @@ function AutocompleteFieldImpl({
52
69
  const seqRef = useRef(0);
53
70
  const optionsRef = useRef(options);
54
71
  optionsRef.current = options;
55
- const searchRef = useRef(search);
56
- searchRef.current = search;
57
- const hasSearch = search !== void 0;
72
+ const adapter = useSduiAdapter();
73
+ const endpointSearch = useMemo(() => {
74
+ if (!endpoint) return void 0;
75
+ const hostFetch = hostFetchOf(adapter);
76
+ if (!hostFetch) return void 0;
77
+ return endpointSearchFn(endpoint, searchParam, hostFetch);
78
+ }, [endpoint, searchParam, adapter]);
79
+ const effectiveSearch = search ?? endpointSearch;
80
+ const searchRef = useRef(effectiveSearch);
81
+ searchRef.current = effectiveSearch;
82
+ const hasSearch = effectiveSearch !== void 0;
58
83
  const isControlled = value !== void 0;
59
84
  const [internalValue, setInternalValue] = useState("");
60
85
  const committed = (isControlled ? value : internalValue) ?? "";
@@ -298,4 +323,4 @@ function AutocompleteFieldImpl({
298
323
  }
299
324
  var AutocompleteField = boxed(AutocompleteFieldImpl, "AutocompleteField");
300
325
 
301
- export { AutocompleteField, toAutocompleteOptions };
326
+ export { AutocompleteField, endpointSearchFn, toAutocompleteOptions };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { CSSProperties, ReactNode, ComponentType } from 'react';
3
- import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-opTPYysN.js';
4
- export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-opTPYysN.js';
3
+ import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-BNdv5-jn.js';
4
+ export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-BNdv5-jn.js';
5
5
  import { S as SduiBoxProps, a as SduiContainerLayoutProps, b as SduiColumnsScale, c as SduiGapScale, d as SduiWeightScale, e as SduiTextAlignScale } from './layout-B1QrIBm6.js';
6
6
  export { C as CHILD_LAYOUT_PROPS, L as LAYOUT_CSS, f as SduiAlignScale, g as SduiChildLayoutProps, h as SduiSizeScale, i as SduiSpanScale, j as childLayoutFrom, k as columnsClass, l as containerLayoutStyle, m as joinClass, n as mergeStyle, t as typographyStyle } from './layout-B1QrIBm6.js';
7
7
  import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-D2EtpvkQ.js';
@@ -1677,6 +1677,12 @@ interface SduiRendererProps {
1677
1677
  */
1678
1678
  declare function SduiRenderer({ page, registry, adapter, syncUrlState, initialState, can, onCustomAction, customValidators, }: SduiRendererProps): React__default.JSX.Element;
1679
1679
 
1680
+ /**
1681
+ * 어휘 전체. **렌더러와 편집기 검증기가 같은 이 집합을 본다** — 목록이 두 벌이면
1682
+ * 한쪽에만 연산을 더했을 때 "렌더는 되는데 저장이 거부되는" 스펙이 생긴다.
1683
+ */
1684
+ declare const CALC_OPS: ReadonlySet<string>;
1685
+
1680
1686
  /**
1681
1687
  * 스펙 구조 검증기 — 서버(또는 편집기)가 만든 스펙의 형태 오류를 렌더 전에 찾아낸다.
1682
1688
  * 렌더러는 개발 모드에서 이 결과를 콘솔에 경고하고, 관리자 편집기는
@@ -1831,4 +1837,4 @@ interface ImageViewerProps {
1831
1837
  */
1832
1838
  declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
1833
1839
 
1834
- 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 ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, 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, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, 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, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, 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 SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, 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, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
1840
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, CALC_OPS, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, 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, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, 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, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, 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 SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, 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, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
- import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, TimePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-HPZTFZZN.js';
2
- export { TimePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-HPZTFZZN.js';
1
+ import { TimePicker } from './chunk-PNVOE5VH.js';
2
+ export { TimePicker } from './chunk-PNVOE5VH.js';
3
3
  import { ImageViewer } from './chunk-THEP3VWY.js';
4
4
  export { ImageViewer } from './chunk-THEP3VWY.js';
5
5
  import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-U7ZK7WYH.js';
6
6
  export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-U7ZK7WYH.js';
7
+ import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-WGRB5UL4.js';
8
+ export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-WGRB5UL4.js';
7
9
  import { HoistedStyle, DateRangePicker, DatePicker, OverlayLayer } from './chunk-XKLUCUVA.js';
8
10
  export { DatePicker, DateRangePicker, OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-XKLUCUVA.js';
9
11
  import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, Dialog } from './chunk-WIDDHR7L.js';
@@ -1783,6 +1785,310 @@ function createRegistry(extensions) {
1783
1785
  return { ...defaultRegistry, ...extensions };
1784
1786
  }
1785
1787
 
1788
+ // src/calc.ts
1789
+ function isPlainObject(v) {
1790
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1791
+ }
1792
+ function toNumber2(v) {
1793
+ if (typeof v === "number") return v;
1794
+ if (typeof v === "string") {
1795
+ const n = Number(v.replace(/,/g, ""));
1796
+ return Number.isFinite(n) ? n : 0;
1797
+ }
1798
+ return 0;
1799
+ }
1800
+ function isNumericText(v) {
1801
+ if (typeof v === "number") return true;
1802
+ if (typeof v !== "string" || v === "") return false;
1803
+ return Number.isFinite(Number(v.replace(/,/g, "")));
1804
+ }
1805
+ function orderingCompare(a, b) {
1806
+ if (typeof a === "string" && typeof b === "string" && !isNumericText(a) && !isNumericText(b)) {
1807
+ return a < b ? -1 : a > b ? 1 : 0;
1808
+ }
1809
+ const x = toNumber2(a);
1810
+ const y = toNumber2(b);
1811
+ return x < y ? -1 : x > y ? 1 : 0;
1812
+ }
1813
+ function looseEquals(a, b) {
1814
+ return String(a ?? "") === String(b ?? "");
1815
+ }
1816
+ function isBlank(v) {
1817
+ return v === null || v === void 0 || v === "";
1818
+ }
1819
+ function fieldOf(row, field) {
1820
+ if (!field) return row;
1821
+ if (!isPlainObject(row)) return void 0;
1822
+ return Object.hasOwn(row, field) ? row[field] : void 0;
1823
+ }
1824
+ var FIELD_OPS = /* @__PURE__ */ new Set([
1825
+ "eq",
1826
+ "ne",
1827
+ "gt",
1828
+ "gte",
1829
+ "lt",
1830
+ "lte",
1831
+ "in",
1832
+ "notIn",
1833
+ "contains",
1834
+ "startsWith",
1835
+ "endsWith",
1836
+ "isNull"
1837
+ ]);
1838
+ function isFieldOpObject(v) {
1839
+ return isPlainObject(v) && Object.keys(v).some((key) => FIELD_OPS.has(key));
1840
+ }
1841
+ function matchesFieldOps(actual, ops, resolve) {
1842
+ return Object.entries(ops).every(([op, rawExpected]) => {
1843
+ if (!FIELD_OPS.has(op)) return true;
1844
+ const expected = resolve(rawExpected);
1845
+ switch (op) {
1846
+ case "eq":
1847
+ return looseEquals(actual, expected);
1848
+ case "ne":
1849
+ return !looseEquals(actual, expected);
1850
+ case "gt":
1851
+ return orderingCompare(actual, expected) > 0;
1852
+ case "gte":
1853
+ return orderingCompare(actual, expected) >= 0;
1854
+ case "lt":
1855
+ return orderingCompare(actual, expected) < 0;
1856
+ case "lte":
1857
+ return orderingCompare(actual, expected) <= 0;
1858
+ case "in":
1859
+ return Array.isArray(expected) && expected.some((item) => looseEquals(actual, item));
1860
+ case "notIn":
1861
+ return !Array.isArray(expected) || !expected.some((item) => looseEquals(actual, item));
1862
+ case "contains":
1863
+ return String(actual ?? "").includes(String(expected ?? ""));
1864
+ case "startsWith":
1865
+ return String(actual ?? "").startsWith(String(expected ?? ""));
1866
+ case "endsWith":
1867
+ return String(actual ?? "").endsWith(String(expected ?? ""));
1868
+ case "isNull":
1869
+ return isBlank(actual) === Boolean(expected);
1870
+ default:
1871
+ return true;
1872
+ }
1873
+ });
1874
+ }
1875
+ function matchesWhere(row, where, resolve) {
1876
+ if (!isPlainObject(where)) return true;
1877
+ return Object.entries(where).every(([key, condition]) => {
1878
+ if (key === "AND") {
1879
+ return Array.isArray(condition) && condition.every((sub) => matchesWhere(row, sub, resolve));
1880
+ }
1881
+ if (key === "OR") {
1882
+ return Array.isArray(condition) && condition.some((sub) => matchesWhere(row, sub, resolve));
1883
+ }
1884
+ if (key === "NOT") {
1885
+ return !matchesWhere(row, condition, resolve);
1886
+ }
1887
+ const actual = fieldOf(row, key);
1888
+ if (isFieldOpObject(condition)) return matchesFieldOps(actual, condition, resolve);
1889
+ return looseEquals(actual, resolve(condition));
1890
+ });
1891
+ }
1892
+ var CALC_OPS = /* @__PURE__ */ new Set([
1893
+ // 산술 (기존)
1894
+ "sum",
1895
+ "sub",
1896
+ "mul",
1897
+ "div",
1898
+ "percent",
1899
+ "count",
1900
+ // 문자 (기존)
1901
+ "concat",
1902
+ "encode",
1903
+ // 비교·논리 (기존)
1904
+ "eq",
1905
+ "gt",
1906
+ "gte",
1907
+ "lt",
1908
+ "lte",
1909
+ "not",
1910
+ // 논리 (신규) — 예전에는 스펙이 eq(concat(eq(a),eq(b)),"falsefalse")로 우회했다
1911
+ "and",
1912
+ "or",
1913
+ // 값 고르기 (신규)
1914
+ "if",
1915
+ "match",
1916
+ "coalesce",
1917
+ // 목록·객체 (신규)
1918
+ "get",
1919
+ "pluck",
1920
+ "filter",
1921
+ "some",
1922
+ "every",
1923
+ "includes",
1924
+ "distinct",
1925
+ "except",
1926
+ "entries",
1927
+ "length",
1928
+ "join",
1929
+ "split",
1930
+ "sortBy"
1931
+ ]);
1932
+ function evaluateCalc(calc, resolved, resolve) {
1933
+ const op = typeof calc.op === "string" ? calc.op : "";
1934
+ const field = typeof calc.field === "string" ? calc.field : void 0;
1935
+ const first = resolved[0];
1936
+ const list = Array.isArray(first) ? first : void 0;
1937
+ const listOrEmpty = () => {
1938
+ if (list) return list;
1939
+ if (first !== void 0 && first !== null) {
1940
+ warnOnce(
1941
+ `calc:${op}:list`,
1942
+ `[sdui] $calc(${op}): \uCCAB \uC778\uC790\uAC00 \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBE48 \uBAA9\uB85D\uC73C\uB85C \uCC98\uB9AC\uD569\uB2C8\uB2E4`
1943
+ );
1944
+ }
1945
+ return [];
1946
+ };
1947
+ const nums = (v) => {
1948
+ if (Array.isArray(v))
1949
+ return v.map((row) => field && isPlainObject(row) ? toNumber2(row[field]) : toNumber2(row));
1950
+ return [toNumber2(v)];
1951
+ };
1952
+ switch (op) {
1953
+ /* ── 산술 ── */
1954
+ case "sum":
1955
+ return resolved.flatMap(nums).reduce((a, b) => a + b, 0);
1956
+ case "count":
1957
+ return resolved.reduce(
1958
+ (a, v) => a + (Array.isArray(v) ? v.length : v == null ? 0 : 1),
1959
+ 0
1960
+ );
1961
+ case "sub": {
1962
+ const items = resolved.map((v) => toNumber2(v));
1963
+ return items.length === 0 ? 0 : items.reduce((a, b) => a - b);
1964
+ }
1965
+ case "mul":
1966
+ return resolved.map((v) => toNumber2(v)).reduce((a, b) => a * b, 1);
1967
+ case "div": {
1968
+ const [a, b] = resolved.map((v) => toNumber2(v));
1969
+ return b === 0 ? 0 : a / b;
1970
+ }
1971
+ case "percent": {
1972
+ const [a, b] = resolved.map((v) => toNumber2(v));
1973
+ return b === 0 ? 0 : Math.round(a / b * 1e3) / 10;
1974
+ }
1975
+ /* ── 문자 ── */
1976
+ case "concat":
1977
+ return resolved.map((v) => v == null ? "" : String(v)).join("");
1978
+ case "encode":
1979
+ return encodeURIComponent(resolved.map((v) => v == null ? "" : String(v)).join(""));
1980
+ /* ── 비교·논리 ── */
1981
+ case "eq":
1982
+ return looseEquals(resolved[0], resolved[1]);
1983
+ case "gt":
1984
+ return orderingCompare(resolved[0], resolved[1]) > 0;
1985
+ case "gte":
1986
+ return orderingCompare(resolved[0], resolved[1]) >= 0;
1987
+ case "lt":
1988
+ return orderingCompare(resolved[0], resolved[1]) < 0;
1989
+ case "lte":
1990
+ return orderingCompare(resolved[0], resolved[1]) <= 0;
1991
+ case "not":
1992
+ return !resolved[0];
1993
+ case "and":
1994
+ return resolved.every(Boolean);
1995
+ case "or":
1996
+ return resolved.some(Boolean);
1997
+ /* ── 값 고르기 ── */
1998
+ case "if":
1999
+ return resolved[0] ? resolve(calc.then ?? null) : resolve(calc.else ?? null);
2000
+ case "match": {
2001
+ const key = String(resolved[0] ?? "");
2002
+ const cases = isPlainObject(calc.cases) ? calc.cases : {};
2003
+ if (Object.hasOwn(cases, key)) return resolve(cases[key]);
2004
+ return resolve(calc.default ?? null);
2005
+ }
2006
+ case "coalesce":
2007
+ return resolved.find((v) => !isBlank(v)) ?? null;
2008
+ /* ── 목록·객체 ── */
2009
+ case "get": {
2010
+ const [container, rawKey] = resolved;
2011
+ const key = rawKey == null ? "" : String(rawKey);
2012
+ if (Array.isArray(container)) {
2013
+ const index = Number(key);
2014
+ if (!Number.isInteger(index) || index < 0 || index >= container.length) return null;
2015
+ return container[index] ?? null;
2016
+ }
2017
+ if (isPlainObject(container)) {
2018
+ return Object.hasOwn(container, key) ? container[key] ?? null : null;
2019
+ }
2020
+ return null;
2021
+ }
2022
+ case "pluck":
2023
+ return listOrEmpty().map((row) => fieldOf(row, field)).filter((v) => v !== null && v !== void 0);
2024
+ case "filter":
2025
+ return listOrEmpty().filter((row) => matchesWhere(row, calc.where, resolve));
2026
+ case "some":
2027
+ return listOrEmpty().some((row) => matchesWhere(row, calc.where, resolve));
2028
+ case "every":
2029
+ return listOrEmpty().every((row) => matchesWhere(row, calc.where, resolve));
2030
+ case "includes": {
2031
+ const needle = resolved[1];
2032
+ if (typeof first === "string") return first.includes(String(needle ?? ""));
2033
+ return listOrEmpty().some((row) => looseEquals(fieldOf(row, field), needle));
2034
+ }
2035
+ case "distinct": {
2036
+ const seen = /* @__PURE__ */ new Set();
2037
+ return listOrEmpty().filter((row) => {
2038
+ const key = String(fieldOf(row, field) ?? "");
2039
+ if (seen.has(key)) return false;
2040
+ seen.add(key);
2041
+ return true;
2042
+ });
2043
+ }
2044
+ case "except": {
2045
+ const remove = new Set(
2046
+ (Array.isArray(resolved[1]) ? resolved[1] : []).map(
2047
+ (row) => String(fieldOf(row, field) ?? "")
2048
+ )
2049
+ );
2050
+ return listOrEmpty().filter(
2051
+ (row) => !remove.has(String(fieldOf(row, field) ?? ""))
2052
+ );
2053
+ }
2054
+ case "entries": {
2055
+ if (!isPlainObject(first)) return [];
2056
+ return Object.entries(first).map(([key, value]) => ({
2057
+ key,
2058
+ value
2059
+ }));
2060
+ }
2061
+ case "length":
2062
+ if (Array.isArray(first)) return first.length;
2063
+ if (typeof first === "string") return first.length;
2064
+ return 0;
2065
+ case "join": {
2066
+ const sep = typeof calc.sep === "string" ? calc.sep : ",";
2067
+ return listOrEmpty().map((row) => {
2068
+ const value = fieldOf(row, field);
2069
+ return value == null ? "" : String(value);
2070
+ }).join(sep);
2071
+ }
2072
+ case "split": {
2073
+ const sep = typeof calc.sep === "string" ? calc.sep : ",";
2074
+ const parts = String(first ?? "").split(sep);
2075
+ if (calc.take === void 0) return parts;
2076
+ const index = toNumber2(calc.take);
2077
+ const at = index < 0 ? parts.length + index : index;
2078
+ return parts[at] ?? null;
2079
+ }
2080
+ case "sortBy": {
2081
+ const desc = Boolean(calc.desc);
2082
+ return [...listOrEmpty()].sort((a, b) => {
2083
+ const order = orderingCompare(fieldOf(a, field), fieldOf(b, field));
2084
+ return desc ? -order : order;
2085
+ });
2086
+ }
2087
+ default:
2088
+ return null;
2089
+ }
2090
+ }
2091
+
1786
2092
  // src/request-action.ts
1787
2093
  function deferChainEntry(container, key) {
1788
2094
  return typeof container.action === "string" && (key === "onSuccess" || key === "onError");
@@ -1829,22 +2135,6 @@ var ACTION_NAMES = /* @__PURE__ */ new Set([
1829
2135
  "arrayUpdate",
1830
2136
  "custom"
1831
2137
  ]);
1832
- var CALC_OPS = /* @__PURE__ */ new Set([
1833
- "sum",
1834
- "sub",
1835
- "mul",
1836
- "div",
1837
- "percent",
1838
- "count",
1839
- "concat",
1840
- "encode",
1841
- "eq",
1842
- "gt",
1843
- "gte",
1844
- "lt",
1845
- "lte",
1846
- "not"
1847
- ]);
1848
2138
  var RULE_NAMES = /* @__PURE__ */ new Set([
1849
2139
  "required",
1850
2140
  "min",
@@ -1864,7 +2154,7 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
1864
2154
  "constructor",
1865
2155
  "prototype"
1866
2156
  ]);
1867
- function isPlainObject(v) {
2157
+ function isPlainObject2(v) {
1868
2158
  return typeof v === "object" && v !== null && !Array.isArray(v);
1869
2159
  }
1870
2160
  function checkUrlValue(key, value, path, issues) {
@@ -1874,7 +2164,7 @@ function checkUrlValue(key, value, path, issues) {
1874
2164
  }
1875
2165
  function validateSpec(page, knownComponents) {
1876
2166
  const issues = [];
1877
- if (!isPlainObject(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2167
+ if (!isPlainObject2(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
1878
2168
  if (page.version !== 1) issues.push({ path: "$.version", message: "version\uC740 1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
1879
2169
  validatePageData(page.data, issues);
1880
2170
  if (!Array.isArray(page.body)) {
@@ -1888,7 +2178,7 @@ function validateSpec(page, knownComponents) {
1888
2178
  }
1889
2179
  function validatePageData(data, issues) {
1890
2180
  if (data === void 0) return;
1891
- if (!isPlainObject(data)) {
2181
+ if (!isPlainObject2(data)) {
1892
2182
  issues.push({ path: "$.data", message: "data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1893
2183
  return;
1894
2184
  }
@@ -1898,7 +2188,7 @@ function validatePageData(data, issues) {
1898
2188
  issues.push({ path, message: "\uC0C1\uD0DC \uD0A4\uB294 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC544\uC57C \uD558\uACE0 $\uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" });
1899
2189
  continue;
1900
2190
  }
1901
- if (!isPlainObject(raw)) {
2191
+ if (!isPlainObject2(raw)) {
1902
2192
  issues.push({ path, message: "\uC870\uD68C\uB294 { endpoint, select?, when?, fallback? } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1903
2193
  continue;
1904
2194
  }
@@ -1910,7 +2200,7 @@ function validatePageData(data, issues) {
1910
2200
  } else if (!raw.endpoint.startsWith("/")) {
1911
2201
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 /\uB85C \uC2DC\uC791\uD558\uB294 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1912
2202
  }
1913
- } else if (!isPlainObject(raw.endpoint)) {
2203
+ } else if (!isPlainObject2(raw.endpoint)) {
1914
2204
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uAC12 \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1915
2205
  } else {
1916
2206
  walkValue(raw.endpoint, `${path}.endpoint`, issues, false);
@@ -1933,7 +2223,7 @@ function validatePageData(data, issues) {
1933
2223
  }
1934
2224
  function walkChild(child, path, issues, known) {
1935
2225
  if (typeof child === "string") return;
1936
- if (!isPlainObject(child)) {
2226
+ if (!isPlainObject2(child)) {
1937
2227
  issues.push({ path, message: "children \uD56D\uBAA9\uC740 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1938
2228
  return;
1939
2229
  }
@@ -1945,7 +2235,7 @@ function walkChild(child, path, issues, known) {
1945
2235
  if (known && !RENDERER_BUILTINS.has(node.component) && !known.has(node.component)) {
1946
2236
  issues.push({ path, message: `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uCEF4\uD3EC\uB10C\uD2B8: ${node.component}` });
1947
2237
  }
1948
- if (node.props !== void 0 && !isPlainObject(node.props)) {
2238
+ if (node.props !== void 0 && !isPlainObject2(node.props)) {
1949
2239
  issues.push({ path: `${path}.props`, message: "props\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1950
2240
  }
1951
2241
  for (const [name, value] of Object.entries(node.props ?? {})) {
@@ -1959,9 +2249,9 @@ function walkChild(child, path, issues, known) {
1959
2249
  checkUrlValue(name, value, `${path}.props.${name}`, issues);
1960
2250
  walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
1961
2251
  }
1962
- if (node.component === "Form" && isPlainObject(node.props) && node.props.rules !== void 0) {
2252
+ if (node.component === "Form" && isPlainObject2(node.props) && node.props.rules !== void 0) {
1963
2253
  const rules = node.props.rules;
1964
- if (!isPlainObject(rules)) {
2254
+ if (!isPlainObject2(rules)) {
1965
2255
  issues.push({ path: `${path}.props.rules`, message: "rules\uB294 { \uD544\uB4DC: \uADDC\uCE59\uBC30\uC5F4 } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1966
2256
  } else {
1967
2257
  for (const [field, list] of Object.entries(rules)) {
@@ -1972,15 +2262,15 @@ function walkChild(child, path, issues, known) {
1972
2262
  }
1973
2263
  }
1974
2264
  if (node.component === "Fragment") {
1975
- const id = isPlainObject(node.props) ? node.props.id : void 0;
1976
- if ((typeof id !== "string" || id === "") && !isPlainObject(id)) {
2265
+ const id = isPlainObject2(node.props) ? node.props.id : void 0;
2266
+ if ((typeof id !== "string" || id === "") && !isPlainObject2(id)) {
1977
2267
  issues.push({
1978
2268
  path: `${path}.props.id`,
1979
2269
  message: "Fragment.id\uB294 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uBB38\uC790\uC5F4(\uB610\uB294 \uAC12 \uCC38\uC870)\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4"
1980
2270
  });
1981
2271
  }
1982
- const args = isPlainObject(node.props) ? node.props.args : void 0;
1983
- if (args !== void 0 && !isPlainObject(args)) {
2272
+ const args = isPlainObject2(node.props) ? node.props.args : void 0;
2273
+ if (args !== void 0 && !isPlainObject2(args)) {
1984
2274
  issues.push({
1985
2275
  path: `${path}.props.args`,
1986
2276
  message: "Fragment.args\uB294 { \uC774\uB984: \uAC12 } \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4"
@@ -1994,7 +2284,7 @@ function walkChild(child, path, issues, known) {
1994
2284
  }
1995
2285
  }
1996
2286
  if (node.slots !== void 0) {
1997
- if (!isPlainObject(node.slots)) {
2287
+ if (!isPlainObject2(node.slots)) {
1998
2288
  issues.push({ path: `${path}.slots`, message: "slots\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1999
2289
  } else {
2000
2290
  for (const [slot, children] of Object.entries(node.slots)) {
@@ -2019,7 +2309,7 @@ function walkValue(value, path, issues, actionEligible) {
2019
2309
  value.forEach((v, i) => walkValue(v, `${path}[${i}]`, issues, actionEligible));
2020
2310
  return;
2021
2311
  }
2022
- if (!isPlainObject(value)) return;
2312
+ if (!isPlainObject2(value)) return;
2023
2313
  if (typeof value.action === "string" && (actionEligible || ACTION_NAMES.has(value.action))) {
2024
2314
  if (actionEligible && !ACTION_NAMES.has(value.action)) {
2025
2315
  issues.push({ path, message: `\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${value.action}` });
@@ -2046,7 +2336,7 @@ function walkValue(value, path, issues, actionEligible) {
2046
2336
  }
2047
2337
  if ("$calc" in value) {
2048
2338
  const calc = value.$calc;
2049
- if (!isPlainObject(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2339
+ if (!isPlainObject2(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2050
2340
  issues.push({ path, message: "$calc.op\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
2051
2341
  } else if (!Array.isArray(calc.args)) {
2052
2342
  issues.push({ path: `${path}.$calc.args`, message: "args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
@@ -2075,12 +2365,12 @@ function walkValue(value, path, issues, actionEligible) {
2075
2365
  }
2076
2366
 
2077
2367
  // src/page-data.ts
2078
- function isPlainObject2(value) {
2368
+ function isPlainObject3(value) {
2079
2369
  return typeof value === "object" && value !== null && !Array.isArray(value);
2080
2370
  }
2081
2371
  function planPageData(data, resolve) {
2082
2372
  if (data === void 0) return [];
2083
- if (!isPlainObject2(data)) {
2373
+ if (!isPlainObject3(data)) {
2084
2374
  warnOnce("page-data:root", "[sdui] page.data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4");
2085
2375
  return [];
2086
2376
  }
@@ -2090,7 +2380,7 @@ function planPageData(data, resolve) {
2090
2380
  warnOnce(`page-data:${key}`, `[sdui] page.data\uC758 \uC0C1\uD0DC \uD0A4 "${key}"\uB294 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2091
2381
  continue;
2092
2382
  }
2093
- if (!isPlainObject2(raw)) {
2383
+ if (!isPlainObject3(raw)) {
2094
2384
  warnOnce(`page-data:${key}`, `[sdui] page.data.${key}\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2095
2385
  continue;
2096
2386
  }
@@ -2125,7 +2415,7 @@ function pickPath(source, path) {
2125
2415
  if (path === void 0 || path === "") return source;
2126
2416
  let value = source;
2127
2417
  for (const segment of path.split(".")) {
2128
- if (!isPlainObject2(value)) return void 0;
2418
+ if (!isPlainObject3(value)) return void 0;
2129
2419
  value = value[segment];
2130
2420
  }
2131
2421
  return value;
@@ -2156,26 +2446,26 @@ function nearestArgsFrame(scope) {
2156
2446
  }
2157
2447
  return void 0;
2158
2448
  }
2159
- function isPlainObject3(v) {
2449
+ function isPlainObject4(v) {
2160
2450
  return typeof v === "object" && v !== null && !Array.isArray(v);
2161
2451
  }
2162
2452
  function isStateRef(v) {
2163
- return isPlainObject3(v) && typeof v.$state === "string";
2453
+ return isPlainObject4(v) && typeof v.$state === "string";
2164
2454
  }
2165
2455
  function isItemRef(v) {
2166
- return isPlainObject3(v) && "$item" in v;
2456
+ return isPlainObject4(v) && "$item" in v;
2167
2457
  }
2168
2458
  function isIndexRef(v) {
2169
- return isPlainObject3(v) && "$index" in v;
2459
+ return isPlainObject4(v) && "$index" in v;
2170
2460
  }
2171
2461
  function isCalcRef(v) {
2172
- return isPlainObject3(v) && isPlainObject3(v.$calc);
2462
+ return isPlainObject4(v) && isPlainObject4(v.$calc);
2173
2463
  }
2174
2464
  function isArgRef(v) {
2175
- return isPlainObject3(v) && typeof v.$arg === "string";
2465
+ return isPlainObject4(v) && typeof v.$arg === "string";
2176
2466
  }
2177
2467
  function isAction(v) {
2178
- return isPlainObject3(v) && typeof v.action === "string";
2468
+ return isPlainObject4(v) && typeof v.action === "string";
2179
2469
  }
2180
2470
  function isActionList(v) {
2181
2471
  return Array.isArray(v) && v.length > 0 && v.every(isAction);
@@ -2199,7 +2489,7 @@ function expandActionEntries(raw, scope) {
2199
2489
  return [];
2200
2490
  }
2201
2491
  function eventValue(arg) {
2202
- if (isPlainObject3(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2492
+ if (isPlainObject4(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2203
2493
  const target = arg.target;
2204
2494
  if (target.type === "checkbox") return target.checked;
2205
2495
  if (target.type === "number" || target.type === "range") {
@@ -2211,14 +2501,6 @@ function eventValue(arg) {
2211
2501
  }
2212
2502
  return arg;
2213
2503
  }
2214
- function toNumber2(v) {
2215
- if (typeof v === "number") return v;
2216
- if (typeof v === "string") {
2217
- const n = Number(v.replace(/,/g, ""));
2218
- return Number.isFinite(n) ? n : 0;
2219
- }
2220
- return 0;
2221
- }
2222
2504
  function toIndex(v) {
2223
2505
  if (typeof v === "number") return Number.isInteger(v) ? v : null;
2224
2506
  if (typeof v === "string" && /^-?\d+$/.test(v.trim())) return Number(v.trim());
@@ -2317,7 +2599,7 @@ function childrenOf(node) {
2317
2599
  }
2318
2600
  function renderChild(child, index, scope, engine) {
2319
2601
  if (typeof child === "string") return child;
2320
- if (!isPlainObject3(child)) {
2602
+ if (!isPlainObject4(child)) {
2321
2603
  warnOnce2(`child:${String(child)}`, `[sdui] \uC798\uBABB\uB41C \uB178\uB4DC(\uBB38\uC790\uC5F4 \uB610\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4): ${String(child)}`);
2322
2604
  return /* @__PURE__ */ jsx(SpecErrorCard, { children: "[sdui] \uC798\uBABB\uB41C \uB178\uB4DC \u2014 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" }, index);
2323
2605
  }
@@ -2332,11 +2614,11 @@ function SduiNodeRenderer({
2332
2614
  engine
2333
2615
  }) {
2334
2616
  const { registry, resolveValue, dispatch, resolveAsset } = engine;
2335
- const slots = isPlainObject3(node.slots) ? node.slots : void 0;
2617
+ const slots = isPlainObject4(node.slots) ? node.slots : void 0;
2336
2618
  const childList = childrenOf(node);
2337
2619
  if (node.component === "ForEach") {
2338
2620
  const source = resolveValue(
2339
- isPlainObject3(node.props) ? node.props.source ?? null : null,
2621
+ isPlainObject4(node.props) ? node.props.source ?? null : null,
2340
2622
  scope
2341
2623
  );
2342
2624
  const rows = Array.isArray(source) ? source : [];
@@ -2346,7 +2628,7 @@ function SduiNodeRenderer({
2346
2628
  }
2347
2629
  return /* @__PURE__ */ jsx(Fragment, { children: rows.map((item, rowIndex) => {
2348
2630
  const rowScope = [...scope, { item, index: rowIndex }];
2349
- const rowKey = isPlainObject3(item) && typeof item.id === "string" ? item.id : rowIndex;
2631
+ const rowKey = isPlainObject4(item) && typeof item.id === "string" ? item.id : rowIndex;
2350
2632
  return /* @__PURE__ */ jsx(React11.Fragment, { children: childList?.map((child, i) => renderChild(child, i, rowScope, engine)) }, rowKey);
2351
2633
  }) });
2352
2634
  }
@@ -2365,7 +2647,7 @@ function SduiNodeRenderer({
2365
2647
  ] });
2366
2648
  }
2367
2649
  const props = {};
2368
- for (const [name, raw] of Object.entries(isPlainObject3(node.props) ? node.props : {})) {
2650
+ for (const [name, raw] of Object.entries(isPlainObject4(node.props) ? node.props : {})) {
2369
2651
  if (DENIED_PROPS2.has(name)) {
2370
2652
  warnOnce2(`prop:${name}`, `[sdui] \uC2A4\uD399\uC5D0\uC11C \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD504\uB86D: ${name} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2371
2653
  continue;
@@ -2404,7 +2686,7 @@ function SduiNodeRenderer({
2404
2686
  if (childLayout) {
2405
2687
  for (const name of CHILD_LAYOUT_PROPS) delete props[name];
2406
2688
  if (childLayout.style) {
2407
- props.style = isPlainObject3(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2689
+ props.style = isPlainObject4(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2408
2690
  }
2409
2691
  if (childLayout.className) {
2410
2692
  props.className = typeof props.className === "string" && props.className ? `${childLayout.className} ${props.className}` : childLayout.className;
@@ -2428,7 +2710,7 @@ var FRAGMENT_MAX_DEPTH = 4;
2428
2710
  function SduiFragment({ node, scope, engine }) {
2429
2711
  const adapter = useSduiAdapter();
2430
2712
  const chain = React11.useContext(SduiFragmentChainContext);
2431
- const raw = isPlainObject3(node.props) ? node.props.id : void 0;
2713
+ const raw = isPlainObject4(node.props) ? node.props.id : void 0;
2432
2714
  const id = engine.resolveValue(raw, scope);
2433
2715
  if (typeof id !== "string" || id === "") {
2434
2716
  warnOnce2("fragment:id", "[sdui] <Fragment>\uC5D0 \uBB38\uC790\uC5F4 id\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB80C\uB354\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
@@ -2448,8 +2730,8 @@ function SduiFragment({ node, scope, engine }) {
2448
2730
  );
2449
2731
  return null;
2450
2732
  }
2451
- const rawArgs = isPlainObject3(node.props) ? node.props.args : void 0;
2452
- if (rawArgs !== void 0 && !isPlainObject3(rawArgs)) {
2733
+ const rawArgs = isPlainObject4(node.props) ? node.props.args : void 0;
2734
+ if (rawArgs !== void 0 && !isPlainObject4(rawArgs)) {
2453
2735
  warnOnce2(`fragment:args:${id}`, `[sdui] <Fragment ${id}>\uC758 args\uB294 \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2454
2736
  }
2455
2737
  const useSpec = adapter?.useSpec;
@@ -2464,7 +2746,7 @@ function SduiFragment({ node, scope, engine }) {
2464
2746
  SduiFragmentBody,
2465
2747
  {
2466
2748
  id,
2467
- args: isPlainObject3(rawArgs) ? rawArgs : void 0,
2749
+ args: isPlainObject4(rawArgs) ? rawArgs : void 0,
2468
2750
  useSpec,
2469
2751
  chain,
2470
2752
  scope,
@@ -2485,13 +2767,13 @@ function SduiFragmentBody({
2485
2767
  const nextChain = React11.useMemo(() => [...chain, id], [chain, id]);
2486
2768
  const bodyScope = React11.useMemo(() => [...scope, { args, callSiteScope: scope }], [scope, args]);
2487
2769
  React11.useEffect(() => {
2488
- if (!DEV || !isPlainObject3(spec) || !Array.isArray(spec.body)) return;
2770
+ if (!DEV || !isPlainObject4(spec) || !Array.isArray(spec.body)) return;
2489
2771
  validateSpec(spec, new Set(Object.keys(engine.registry))).forEach(
2490
2772
  (issue) => console.warn(`[sdui:spec:${id}] ${issue.path} \u2014 ${issue.message}`)
2491
2773
  );
2492
2774
  }, [spec, engine.registry, id]);
2493
2775
  if (spec === void 0) return null;
2494
- if (spec === null || !isPlainObject3(spec) || !Array.isArray(spec.body)) {
2776
+ if (spec === null || !isPlainObject4(spec) || !Array.isArray(spec.body)) {
2495
2777
  warnOnce2(`fragment:missing:${id}`, `[sdui] Fragment \uC2A4\uD399\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id} \u2014 \uB80C\uB354\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`);
2496
2778
  return null;
2497
2779
  }
@@ -2621,7 +2903,7 @@ function SduiRenderer({
2621
2903
  if (head in current) {
2622
2904
  let value = current[head];
2623
2905
  for (const segment of segments.slice(cut)) {
2624
- if (!isPlainObject3(value)) return void 0;
2906
+ if (!isPlainObject4(value)) return void 0;
2625
2907
  value = value[segment];
2626
2908
  }
2627
2909
  return value;
@@ -2639,7 +2921,7 @@ function SduiRenderer({
2639
2921
  const frame = nearestItemFrame(scope);
2640
2922
  if (!frame) return null;
2641
2923
  if (value.$item === true) return frame.item;
2642
- return isPlainObject3(frame.item) ? frame.item[value.$item] ?? null : null;
2924
+ return isPlainObject4(frame.item) ? frame.item[value.$item] ?? null : null;
2643
2925
  }
2644
2926
  if (isIndexRef(value)) {
2645
2927
  return nearestItemFrame(scope)?.index ?? null;
@@ -2653,64 +2935,17 @@ function SduiRenderer({
2653
2935
  return void 0;
2654
2936
  }
2655
2937
  if (isCalcRef(value)) {
2656
- const { op: rawOp, args, field: rawField } = value.$calc;
2657
- const op = typeof rawOp === "string" ? rawOp : "";
2658
- if (!Array.isArray(args)) {
2938
+ const calc = value.$calc;
2939
+ const op = typeof calc.op === "string" ? calc.op : "";
2940
+ if (!Array.isArray(calc.args)) {
2659
2941
  warnOnce2(`calc:${op}`, `[sdui] $calc(${op || "?"}): args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC12 \uC5C6\uC74C\uC73C\uB85C \uCC98\uB9AC\uD569\uB2C8\uB2E4`);
2660
2942
  return null;
2661
2943
  }
2662
- const field = typeof rawField === "string" ? rawField : void 0;
2663
- const resolved = args.map((arg) => resolveValue(arg, scope));
2664
- const nums = (v) => {
2665
- if (Array.isArray(v))
2666
- return v.map((row) => field && isPlainObject3(row) ? toNumber2(row[field]) : toNumber2(row));
2667
- return [toNumber2(v)];
2668
- };
2669
- switch (op) {
2670
- case "sum":
2671
- return resolved.flatMap(nums).reduce((a, b) => a + b, 0);
2672
- case "count":
2673
- return resolved.reduce((a, v) => a + (Array.isArray(v) ? v.length : v == null ? 0 : 1), 0);
2674
- case "sub": {
2675
- const list = resolved.map((v) => toNumber2(v));
2676
- return list.length === 0 ? 0 : list.reduce((a, b) => a - b);
2677
- }
2678
- case "mul":
2679
- return resolved.map((v) => toNumber2(v)).reduce((a, b) => a * b, 1);
2680
- case "div": {
2681
- const [a, b] = resolved.map((v) => toNumber2(v));
2682
- return b === 0 ? 0 : a / b;
2683
- }
2684
- case "percent": {
2685
- const [a, b] = resolved.map((v) => toNumber2(v));
2686
- return b === 0 ? 0 : Math.round(a / b * 1e3) / 10;
2687
- }
2688
- case "concat":
2689
- return resolved.map((v) => v == null ? "" : String(v)).join("");
2690
- case "encode":
2691
- return encodeURIComponent(resolved.map((v) => v == null ? "" : String(v)).join(""));
2692
- case "eq": {
2693
- const [a, b] = resolved;
2694
- return String(a ?? "") === String(b ?? "");
2695
- }
2696
- // 수량 비교 — 정원 초과 차단(Button.disabled), 임계값 경고(Show.when) 등
2697
- case "gt":
2698
- return toNumber2(resolved[0]) > toNumber2(resolved[1]);
2699
- case "gte":
2700
- return toNumber2(resolved[0]) >= toNumber2(resolved[1]);
2701
- case "lt":
2702
- return toNumber2(resolved[0]) < toNumber2(resolved[1]);
2703
- case "lte":
2704
- return toNumber2(resolved[0]) <= toNumber2(resolved[1]);
2705
- /** 부정 — 조건을 뒤집어 "없을 때만" 렌더링하는 데 쓴다 */
2706
- case "not":
2707
- return !resolved[0];
2708
- default:
2709
- return null;
2710
- }
2944
+ const resolved = calc.args.map((arg) => resolveValue(arg, scope));
2945
+ return evaluateCalc(calc, resolved, (late) => resolveValue(late, scope));
2711
2946
  }
2712
2947
  if (Array.isArray(value)) return value.map((v) => resolveValue(v, scope));
2713
- if (isPlainObject3(value)) {
2948
+ if (isPlainObject4(value)) {
2714
2949
  const out = {};
2715
2950
  for (const [k, v] of Object.entries(value)) {
2716
2951
  out[k] = deferChainEntry(value, k) ? v : resolveValue(v, scope);
@@ -2879,7 +3114,7 @@ function SduiRenderer({
2879
3114
  break;
2880
3115
  }
2881
3116
  const row = current[index];
2882
- if (!isPlainObject3(row)) break;
3117
+ if (!isPlainObject4(row)) break;
2883
3118
  const nextValue = (action.fromEvent ? eventValue(arg) : action.value) ?? null;
2884
3119
  const next = [...current];
2885
3120
  next[index] = { ...row, [action.field]: nextValue };
@@ -3022,4 +3257,4 @@ function SduiRenderer({
3022
3257
  );
3023
3258
  }
3024
3259
 
3025
- export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, SortHandle, SortableList, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, imageRenderer, normalizePageSizes, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
3260
+ export { Actions, CALC_OPS, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, SortHandle, SortableList, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, imageRenderer, normalizePageSizes, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
package/dist/meta.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { b as SduiPropValue, i as SduiNode } from './types-opTPYysN.js';
1
+ import { b as SduiPropValue, i as SduiNode } from './types-BNdv5-jn.js';
2
2
 
3
3
  /**
4
4
  * 시각 빌더용 컴포넌트 메타데이터 스키마.
@@ -405,9 +405,9 @@ declare const richComponentMeta: {
405
405
  * 받지 않게 하려는 것이다. 여기도 값 import는 없다(`import type`만).
406
406
  *
407
407
  * 프롭 목록은 `src/widgets/*.tsx`의 **실제 프롭 인터페이스**와, ERP 스펙 84개를 훑어
408
- * 뽑은 **실사용 프롭 집합**의 합집합이다. 대조 결과 이 21종에서 스펙이 쓰는 프롭 중
409
- * 구현에 없는 것은 `AutocompleteField.endpoint` 하나뿐이고(아래 `legacy` 주석 참고),
410
- * 나머지는 전부 구현에 존재한다.
408
+ * 뽑은 **실사용 프롭 집합**의 합집합이다. 스펙이 쓰는데 구현에 없는 프롭은 이제
409
+ * **한 개도 없다** — 마지막 하나였던 `AutocompleteField.endpoint`가 0.22.0에서
410
+ * 구현으로 들어왔다(소비 앱이 들고 있던 어댑터가 그만큼 사라진다).
411
411
  *
412
412
  * ## 함수 프롭을 메타데이터에 넣지 않은 자리
413
413
  *
package/dist/meta.js CHANGED
@@ -3298,7 +3298,7 @@ var AutocompleteFieldMeta = {
3298
3298
  label: "\uC790\uB3D9\uC644\uC131",
3299
3299
  category: "input",
3300
3300
  icon: "search",
3301
- description: "\uAC80\uC0C9\uD574\uC11C \uD558\uB098\uB97C \uACE0\uB974\uB294 \uC785\uB825. \uACE0\uB978 \uB4A4\uC5D0\uB294 \uC120\uD0DD \uCE74\uB4DC\uB85C \uC811\uD78C\uB2E4. \uC6D0\uACA9 \uC870\uD68C\uB294 \uC571\uC774 \uC5B9\uC740 \uC5B4\uB311\uD130\uAC00 \uB2F4\uB2F9\uD55C\uB2E4.",
3301
+ description: "\uAC80\uC0C9\uD574\uC11C \uD558\uB098\uB97C \uACE0\uB974\uB294 \uC785\uB825. \uACE0\uB978 \uB4A4\uC5D0\uB294 \uC120\uD0DD \uCE74\uB4DC\uB85C \uC811\uD78C\uB2E4. \uAC80\uC0C9 \uACBD\uB85C\uB97C \uC8FC\uBA74 \uC6D0\uACA9 \uC870\uD68C\uAE4C\uC9C0 \uD55C\uB2E4.",
3302
3302
  props: [
3303
3303
  {
3304
3304
  name: "options",
@@ -3316,9 +3316,15 @@ var AutocompleteFieldMeta = {
3316
3316
  {
3317
3317
  name: "endpoint",
3318
3318
  kind: "string",
3319
- legacy: true,
3320
3319
  label: "\uAC80\uC0C9 \uACBD\uB85C",
3321
- description: "\uB77C\uC774\uBE0C\uB7EC\uB9AC \uAD6C\uD604\uC5D0\uB294 \uC5C6\uB2E4 \u2014 \uC774 \uC790\uB9AC\uC758 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uD504\uB86D\uC740 `search`(\uD568\uC218)\uB77C JSON\uC73C\uB85C \uD45C\uD604\uD560 \uC218 \uC5C6\uC5B4\uC11C, \uC18C\uBE44 \uC571\uC774 \uC5B4\uB311\uD130\uB97C \uC50C\uC6CC endpoint\uB97C search\uB85C \uBC14\uAFD4 \uB4F1\uB85D\uD55C\uB2E4(ERP\uC758 `@/components/widgets/AutocompleteField`). \uC2A4\uD399 \uC5B4\uD718\uB294 endpoint \uCABD\uC774\uBBC0\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4.",
3320
+ description: "\uC8FC\uBA74 \uC5B4\uB311\uD130\uC758 fetch\uB85C \uC9C1\uC811 \uC870\uD68C\uD55C\uB2E4(`GET {\uACBD\uB85C}?q={\uD0A4\uC6CC\uB4DC}`). 0.22.0 \uC804\uC5D0\uB294 \uC774 \uD504\uB86D\uC774 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uAD6C\uD604\uC5D0 \uC5C6\uC5B4\uC11C \uC18C\uBE44 \uC571\uC774 \uC5B4\uB311\uD130\uB97C \uC50C\uC6CC endpoint\uB97C search \uD568\uC218\uB85C \uBC14\uAFD4 \uB4F1\uB85D\uD588\uB2E4 \u2014 \uC774\uC81C \uD544\uC694 \uC5C6\uB2E4.",
3321
+ group: "data"
3322
+ },
3323
+ {
3324
+ name: "searchParam",
3325
+ kind: "string",
3326
+ label: "\uD0A4\uC6CC\uB4DC \uD30C\uB77C\uBBF8\uD130",
3327
+ description: "endpoint \uC870\uD68C\uC5D0 \uBD99\uB294 \uD0A4\uC6CC\uB4DC \uCFFC\uB9AC \uC774\uB984. \uAE30\uBCF8 'q'.",
3322
3328
  group: "data"
3323
3329
  },
3324
3330
  {
package/dist/rich.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useSduiAssetResolver } from './chunk-HPZTFZZN.js';
2
- export { TimePicker } from './chunk-HPZTFZZN.js';
1
+ export { TimePicker } from './chunk-PNVOE5VH.js';
2
+ import { useSduiAssetResolver } from './chunk-WGRB5UL4.js';
3
3
  import { HoistedStyle } from './chunk-XKLUCUVA.js';
4
4
  export { DatePicker, DateRangePicker } from './chunk-XKLUCUVA.js';
5
5
  import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
@@ -64,11 +64,67 @@ interface SduiIndexRef {
64
64
  interface SduiCalcRef {
65
65
  $calc: {
66
66
  op: 'sum' | 'sub' | 'mul' | 'div' | 'percent' | 'count' | 'concat' | 'encode'
67
- /** 비교·논리 — Show의 when, Button의 disabled 등에 바인딩한다 */
68
- | 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'not';
67
+ /**
68
+ * 비교·논리 — Show의 when, Button의 disabled 등에 바인딩한다.
69
+ * 순서 비교(gt/gte/lt/lte)는 숫자로 재되, **양쪽이 모두 숫자가 아닌 문자열일
70
+ * 때만** 사전순으로 본다 — ISO 날짜·시각 문자열의 대소가 이 규칙으로 성립한다.
71
+ */
72
+ | 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'not' | 'and' | 'or'
73
+ /**
74
+ * 값 고르기. `if`는 `then`/`else`, `match`는 `cases`(+`default`)를 쓴다 —
75
+ * 둘 다 **고른 가지만** 해석되므로 안 고른 가지의 조회는 나가지 않는다.
76
+ * `coalesce`는 args에서 처음으로 비지 않은 값(null·undefined·'' 제외)을 준다.
77
+ */
78
+ | 'if' | 'match' | 'coalesce'
79
+ /**
80
+ * 목록·객체. JSON에는 람다가 없으므로 술어는 `where` 객체로 쓴다
81
+ * (Prisma의 where와 같은 모양 — 아래 필드 설명 참고).
82
+ *
83
+ * - `get` args[0]에서 args[1] 키/인덱스로 꺼낸다(런타임 값이 키가 될 수 있다)
84
+ * - `pluck` 객체 배열에서 `field` 값만 뽑아 값 배열로 만든다(빈 값은 떨어뜨린다)
85
+ * - `filter` `where`에 맞는 행만 남긴다
86
+ * - `some`/`every` `where`에 맞는 행이 있는지 / 전부인지
87
+ * - `includes` 목록(또는 문자열)이 args[1]을 담고 있는지
88
+ * - `distinct` `field`(없으면 값 자체) 기준 중복 제거
89
+ * - `except` args[0]에서 args[1]에 있는 것을 뺀다(`field`로 맞춘다)
90
+ * - `entries` 객체를 `{key, value}` 배열로 편다 — ForEach가 돌 수 있는 모양
91
+ * - `length` 배열·문자열의 길이
92
+ * - `join` `sep`(기본 ',')으로 잇는다
93
+ * - `split` `sep`(기본 ',')으로 가른다. `take`를 주면 그 조각 하나(음수는 뒤에서)
94
+ * - `sortBy` `field` 기준 정렬(`desc: true`면 내림차순). 원본은 건드리지 않는다
95
+ */
96
+ | 'get' | 'pluck' | 'filter' | 'some' | 'every' | 'includes' | 'distinct' | 'except' | 'entries' | 'length' | 'join' | 'split' | 'sortBy';
69
97
  args: SduiPropValue[];
70
- /** sum/count에서 객체 배열의 필드를 지정 */
98
+ /** sum/count·목록 연산에서 객체 배열의 필드를 지정 */
71
99
  field?: string;
100
+ /**
101
+ * `filter`·`some`·`every`의 술어. 키가 필드, 값이 연산 객체다.
102
+ *
103
+ * { "deptId": { "eq": { "$state": "sel" } },
104
+ * "level": { "gte": 3 },
105
+ * "OR": [ { "status": "open" }, { "owner": { "isNull": true } } ] }
106
+ *
107
+ * 필드 연산: eq · ne · gt · gte · lt · lte · in · notIn · contains ·
108
+ * startsWith · endsWith · isNull. 값이 연산 객체가 아니면 `eq`로 읽는다.
109
+ * 같은 객체의 키들은 AND이고, `AND`/`OR`는 술어 배열, `NOT`은 술어 하나다.
110
+ * 술어 안의 값에도 참조를 쓸 수 있는데, 이때 `$item`은 거르고 있는 행이 아니라
111
+ * **바깥 ForEach의 행**을 가리킨다(거르는 행은 필드 이름으로 지목한다).
112
+ */
113
+ where?: SduiPropValue;
114
+ /** `if`가 참일 때의 값 (고를 때만 해석된다) */
115
+ then?: SduiPropValue;
116
+ /** `if`가 거짓일 때의 값 (고를 때만 해석된다) */
117
+ else?: SduiPropValue;
118
+ /** `match`의 대응표 — 키는 args[0]을 문자열로 눕힌 값 */
119
+ cases?: Record<string, SduiPropValue>;
120
+ /** `match`에서 어느 case에도 안 맞을 때의 값 */
121
+ default?: SduiPropValue;
122
+ /** `join`/`split`의 구분자 (기본 ',') */
123
+ sep?: string;
124
+ /** `split`에서 꺼낼 조각의 인덱스. 음수는 뒤에서부터 센다 */
125
+ take?: number;
126
+ /** `sortBy`를 내림차순으로 */
127
+ desc?: boolean;
72
128
  };
73
129
  }
74
130
  /** 폼 필드 검증 규칙. */
@@ -26,7 +26,20 @@ interface AutocompleteFieldProps {
26
26
  * ERP의 `endpoint` 프롭은 이 함수를 만드는 앱 쪽 어댑터가 계속 소유한다(위 주석 참고).
27
27
  */
28
28
  search?: AutocompleteSearchFn;
29
- /** 정적 옵션(데모/폴백) — `search`가 없을 때 로컬 검색 대상. */
29
+ /**
30
+ * 조회 경로. 주면 **어댑터의 `fetch`로 직접 조회한다** — 소비 앱이 `search`를
31
+ * 만들어 넘기지 않아도 된다(`GET {endpoint}?{searchParam}={키워드}`).
32
+ *
33
+ * `search`가 함께 오면 그쪽이 이긴다 — 응답 모양이나 오류 분기가 특수한 화면은
34
+ * 여전히 자기 함수를 넘길 수 있다.
35
+ *
36
+ * 어댑터가 없거나 `fetch`를 안 준 환경(편집기 캔버스 등)에서는 조회하지 않고
37
+ * `options` 로컬 검색으로 떨어진다 — 미리보기에서 실요청이 나가면 안 된다.
38
+ */
39
+ endpoint?: string;
40
+ /** `endpoint` 조회의 키워드 파라미터 이름. 기본 'q'. */
41
+ searchParam?: string;
42
+ /** 정적 옵션(데모/폴백) — `search`·`endpoint`가 없을 때 로컬 검색 대상. */
30
43
  options?: AutocompleteOption[];
31
44
  /** 옵션에서 표시 라벨을 읽을 키. 기본 'label'. */
32
45
  labelKey?: string;
@@ -48,6 +61,20 @@ interface AutocompleteFieldProps {
48
61
  invalid?: boolean;
49
62
  disabled?: boolean;
50
63
  }
64
+ type HostFetch = (path: string, init: {
65
+ method: string;
66
+ signal?: AbortSignal;
67
+ }) => Promise<unknown>;
68
+ /**
69
+ * `endpoint` → 검색 함수. 컴포넌트 밖에 두어 DOM 없이 그대로 잰다
70
+ * (`page-data.ts`·`calc.ts`와 같은 이유).
71
+ *
72
+ * 503만 별도로 본다. 어댑터 계약상 실패는 Error 하나라 상태 코드가 사라지지만,
73
+ * 503은 "서비스가 아직 없다"는 표준 의미라 "불러왔는데 없다"와 사용자에게 다른
74
+ * 말을 해야 한다. 호스트 오류 객체에 `status`가 있으면 읽고, 없으면 평소대로
75
+ * 던진다 — 특정 앱의 오류 타입을 알지 않는다.
76
+ */
77
+ declare function endpointSearchFn(endpoint: string, searchParam: string, hostFetch: HostFetch): AutocompleteSearchFn;
51
78
  /**
52
79
  * 응답이 배열이 아니면 items/data/results/rows 래핑까지 풀어 준다.
53
80
  * 조회를 앱이 맡게 됐으므로 이 정규화도 함께 내보낸다 — 앱마다 다시 짤 규칙이 아니다.
@@ -59,4 +86,4 @@ declare function toAutocompleteOptions(payload: unknown): AutocompleteOption[];
59
86
  */
60
87
  declare const AutocompleteField: React__default.FC<AutocompleteFieldProps & SduiBoxProps>;
61
88
 
62
- export { AutocompleteField, type AutocompleteFieldProps, type AutocompleteOption, type AutocompleteSearchFn, type AutocompleteSearchResult, toAutocompleteOptions };
89
+ export { AutocompleteField, type AutocompleteFieldProps, type AutocompleteOption, type AutocompleteSearchFn, type AutocompleteSearchResult, endpointSearchFn, toAutocompleteOptions };
@@ -1,5 +1,6 @@
1
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-SRGXODY2.js';
1
+ export { AutocompleteField, endpointSearchFn, toAutocompleteOptions } from '../chunk-WZKDOZL2.js';
2
2
  import '../chunk-U7ZK7WYH.js';
3
+ import '../chunk-WGRB5UL4.js';
3
4
  import '../chunk-XKLUCUVA.js';
4
5
  import '../chunk-WIDDHR7L.js';
5
6
  import '../chunk-SAN4IX6N.js';
@@ -35,8 +35,9 @@ export { PeriodFilter } from '../chunk-PFUULIZ2.js';
35
35
  import { PrintSheet } from '../chunk-MYJ2KEIP.js';
36
36
  export { PrintSheet } from '../chunk-MYJ2KEIP.js';
37
37
  import '../chunk-THEP3VWY.js';
38
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-SRGXODY2.js';
38
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-WZKDOZL2.js';
39
39
  import '../chunk-U7ZK7WYH.js';
40
+ import '../chunk-WGRB5UL4.js';
40
41
  import '../chunk-XKLUCUVA.js';
41
42
  import { ColumnBoard } from '../chunk-C4JRSZGS.js';
42
43
  export { ColumnBoard } from '../chunk-C4JRSZGS.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",