@wipco/sdui 0.21.0 → 0.23.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/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-36MXPCJP.js';
2
+ export { TimePicker, loadOptionalModule } from './chunk-36MXPCJP.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-DVOPZFQS.js';
8
+ export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-DVOPZFQS.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");
@@ -1802,6 +2108,17 @@ async function runRequestAction(action, deps) {
1802
2108
  (action.onSuccess ?? []).forEach((a) => deps.dispatch(a, response));
1803
2109
  } catch (error) {
1804
2110
  const message = error instanceof Error ? error.message : "\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4";
2111
+ if (typeof action.errorKey === "string" && action.errorKey !== "") {
2112
+ const detail = error;
2113
+ const value = { message };
2114
+ if (typeof detail?.status === "number") value.status = detail.status;
2115
+ if (detail?.body !== void 0) value.body = detail.body;
2116
+ deps.dispatch({
2117
+ action: "setState",
2118
+ key: action.errorKey,
2119
+ value
2120
+ });
2121
+ }
1805
2122
  if (action.onError?.length) {
1806
2123
  action.onError.forEach((a) => deps.dispatch(a, message));
1807
2124
  } else {
@@ -1829,22 +2146,6 @@ var ACTION_NAMES = /* @__PURE__ */ new Set([
1829
2146
  "arrayUpdate",
1830
2147
  "custom"
1831
2148
  ]);
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
2149
  var RULE_NAMES = /* @__PURE__ */ new Set([
1849
2150
  "required",
1850
2151
  "min",
@@ -1864,7 +2165,7 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
1864
2165
  "constructor",
1865
2166
  "prototype"
1866
2167
  ]);
1867
- function isPlainObject(v) {
2168
+ function isPlainObject2(v) {
1868
2169
  return typeof v === "object" && v !== null && !Array.isArray(v);
1869
2170
  }
1870
2171
  function checkUrlValue(key, value, path, issues) {
@@ -1874,7 +2175,7 @@ function checkUrlValue(key, value, path, issues) {
1874
2175
  }
1875
2176
  function validateSpec(page, knownComponents) {
1876
2177
  const issues = [];
1877
- if (!isPlainObject(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2178
+ if (!isPlainObject2(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
1878
2179
  if (page.version !== 1) issues.push({ path: "$.version", message: "version\uC740 1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
1879
2180
  validatePageData(page.data, issues);
1880
2181
  if (!Array.isArray(page.body)) {
@@ -1888,7 +2189,7 @@ function validateSpec(page, knownComponents) {
1888
2189
  }
1889
2190
  function validatePageData(data, issues) {
1890
2191
  if (data === void 0) return;
1891
- if (!isPlainObject(data)) {
2192
+ if (!isPlainObject2(data)) {
1892
2193
  issues.push({ path: "$.data", message: "data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1893
2194
  return;
1894
2195
  }
@@ -1898,7 +2199,7 @@ function validatePageData(data, issues) {
1898
2199
  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
2200
  continue;
1900
2201
  }
1901
- if (!isPlainObject(raw)) {
2202
+ if (!isPlainObject2(raw)) {
1902
2203
  issues.push({ path, message: "\uC870\uD68C\uB294 { endpoint, select?, when?, fallback? } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1903
2204
  continue;
1904
2205
  }
@@ -1910,7 +2211,7 @@ function validatePageData(data, issues) {
1910
2211
  } else if (!raw.endpoint.startsWith("/")) {
1911
2212
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 /\uB85C \uC2DC\uC791\uD558\uB294 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1912
2213
  }
1913
- } else if (!isPlainObject(raw.endpoint)) {
2214
+ } else if (!isPlainObject2(raw.endpoint)) {
1914
2215
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uAC12 \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1915
2216
  } else {
1916
2217
  walkValue(raw.endpoint, `${path}.endpoint`, issues, false);
@@ -1933,7 +2234,7 @@ function validatePageData(data, issues) {
1933
2234
  }
1934
2235
  function walkChild(child, path, issues, known) {
1935
2236
  if (typeof child === "string") return;
1936
- if (!isPlainObject(child)) {
2237
+ if (!isPlainObject2(child)) {
1937
2238
  issues.push({ path, message: "children \uD56D\uBAA9\uC740 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1938
2239
  return;
1939
2240
  }
@@ -1945,7 +2246,7 @@ function walkChild(child, path, issues, known) {
1945
2246
  if (known && !RENDERER_BUILTINS.has(node.component) && !known.has(node.component)) {
1946
2247
  issues.push({ path, message: `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uCEF4\uD3EC\uB10C\uD2B8: ${node.component}` });
1947
2248
  }
1948
- if (node.props !== void 0 && !isPlainObject(node.props)) {
2249
+ if (node.props !== void 0 && !isPlainObject2(node.props)) {
1949
2250
  issues.push({ path: `${path}.props`, message: "props\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1950
2251
  }
1951
2252
  for (const [name, value] of Object.entries(node.props ?? {})) {
@@ -1959,9 +2260,9 @@ function walkChild(child, path, issues, known) {
1959
2260
  checkUrlValue(name, value, `${path}.props.${name}`, issues);
1960
2261
  walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
1961
2262
  }
1962
- if (node.component === "Form" && isPlainObject(node.props) && node.props.rules !== void 0) {
2263
+ if (node.component === "Form" && isPlainObject2(node.props) && node.props.rules !== void 0) {
1963
2264
  const rules = node.props.rules;
1964
- if (!isPlainObject(rules)) {
2265
+ if (!isPlainObject2(rules)) {
1965
2266
  issues.push({ path: `${path}.props.rules`, message: "rules\uB294 { \uD544\uB4DC: \uADDC\uCE59\uBC30\uC5F4 } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1966
2267
  } else {
1967
2268
  for (const [field, list] of Object.entries(rules)) {
@@ -1972,15 +2273,15 @@ function walkChild(child, path, issues, known) {
1972
2273
  }
1973
2274
  }
1974
2275
  if (node.component === "Fragment") {
1975
- const id = isPlainObject(node.props) ? node.props.id : void 0;
1976
- if ((typeof id !== "string" || id === "") && !isPlainObject(id)) {
2276
+ const id = isPlainObject2(node.props) ? node.props.id : void 0;
2277
+ if ((typeof id !== "string" || id === "") && !isPlainObject2(id)) {
1977
2278
  issues.push({
1978
2279
  path: `${path}.props.id`,
1979
2280
  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
2281
  });
1981
2282
  }
1982
- const args = isPlainObject(node.props) ? node.props.args : void 0;
1983
- if (args !== void 0 && !isPlainObject(args)) {
2283
+ const args = isPlainObject2(node.props) ? node.props.args : void 0;
2284
+ if (args !== void 0 && !isPlainObject2(args)) {
1984
2285
  issues.push({
1985
2286
  path: `${path}.props.args`,
1986
2287
  message: "Fragment.args\uB294 { \uC774\uB984: \uAC12 } \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4"
@@ -1994,7 +2295,7 @@ function walkChild(child, path, issues, known) {
1994
2295
  }
1995
2296
  }
1996
2297
  if (node.slots !== void 0) {
1997
- if (!isPlainObject(node.slots)) {
2298
+ if (!isPlainObject2(node.slots)) {
1998
2299
  issues.push({ path: `${path}.slots`, message: "slots\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
1999
2300
  } else {
2000
2301
  for (const [slot, children] of Object.entries(node.slots)) {
@@ -2019,7 +2320,7 @@ function walkValue(value, path, issues, actionEligible) {
2019
2320
  value.forEach((v, i) => walkValue(v, `${path}[${i}]`, issues, actionEligible));
2020
2321
  return;
2021
2322
  }
2022
- if (!isPlainObject(value)) return;
2323
+ if (!isPlainObject2(value)) return;
2023
2324
  if (typeof value.action === "string" && (actionEligible || ACTION_NAMES.has(value.action))) {
2024
2325
  if (actionEligible && !ACTION_NAMES.has(value.action)) {
2025
2326
  issues.push({ path, message: `\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${value.action}` });
@@ -2046,7 +2347,7 @@ function walkValue(value, path, issues, actionEligible) {
2046
2347
  }
2047
2348
  if ("$calc" in value) {
2048
2349
  const calc = value.$calc;
2049
- if (!isPlainObject(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2350
+ if (!isPlainObject2(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2050
2351
  issues.push({ path, message: "$calc.op\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
2051
2352
  } else if (!Array.isArray(calc.args)) {
2052
2353
  issues.push({ path: `${path}.$calc.args`, message: "args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
@@ -2075,12 +2376,12 @@ function walkValue(value, path, issues, actionEligible) {
2075
2376
  }
2076
2377
 
2077
2378
  // src/page-data.ts
2078
- function isPlainObject2(value) {
2379
+ function isPlainObject3(value) {
2079
2380
  return typeof value === "object" && value !== null && !Array.isArray(value);
2080
2381
  }
2081
2382
  function planPageData(data, resolve) {
2082
2383
  if (data === void 0) return [];
2083
- if (!isPlainObject2(data)) {
2384
+ if (!isPlainObject3(data)) {
2084
2385
  warnOnce("page-data:root", "[sdui] page.data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4");
2085
2386
  return [];
2086
2387
  }
@@ -2090,7 +2391,7 @@ function planPageData(data, resolve) {
2090
2391
  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
2392
  continue;
2092
2393
  }
2093
- if (!isPlainObject2(raw)) {
2394
+ if (!isPlainObject3(raw)) {
2094
2395
  warnOnce(`page-data:${key}`, `[sdui] page.data.${key}\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2095
2396
  continue;
2096
2397
  }
@@ -2125,7 +2426,7 @@ function pickPath(source, path) {
2125
2426
  if (path === void 0 || path === "") return source;
2126
2427
  let value = source;
2127
2428
  for (const segment of path.split(".")) {
2128
- if (!isPlainObject2(value)) return void 0;
2429
+ if (!isPlainObject3(value)) return void 0;
2129
2430
  value = value[segment];
2130
2431
  }
2131
2432
  return value;
@@ -2156,26 +2457,26 @@ function nearestArgsFrame(scope) {
2156
2457
  }
2157
2458
  return void 0;
2158
2459
  }
2159
- function isPlainObject3(v) {
2460
+ function isPlainObject4(v) {
2160
2461
  return typeof v === "object" && v !== null && !Array.isArray(v);
2161
2462
  }
2162
2463
  function isStateRef(v) {
2163
- return isPlainObject3(v) && typeof v.$state === "string";
2464
+ return isPlainObject4(v) && typeof v.$state === "string";
2164
2465
  }
2165
2466
  function isItemRef(v) {
2166
- return isPlainObject3(v) && "$item" in v;
2467
+ return isPlainObject4(v) && "$item" in v;
2167
2468
  }
2168
2469
  function isIndexRef(v) {
2169
- return isPlainObject3(v) && "$index" in v;
2470
+ return isPlainObject4(v) && "$index" in v;
2170
2471
  }
2171
2472
  function isCalcRef(v) {
2172
- return isPlainObject3(v) && isPlainObject3(v.$calc);
2473
+ return isPlainObject4(v) && isPlainObject4(v.$calc);
2173
2474
  }
2174
2475
  function isArgRef(v) {
2175
- return isPlainObject3(v) && typeof v.$arg === "string";
2476
+ return isPlainObject4(v) && typeof v.$arg === "string";
2176
2477
  }
2177
2478
  function isAction(v) {
2178
- return isPlainObject3(v) && typeof v.action === "string";
2479
+ return isPlainObject4(v) && typeof v.action === "string";
2179
2480
  }
2180
2481
  function isActionList(v) {
2181
2482
  return Array.isArray(v) && v.length > 0 && v.every(isAction);
@@ -2199,7 +2500,7 @@ function expandActionEntries(raw, scope) {
2199
2500
  return [];
2200
2501
  }
2201
2502
  function eventValue(arg) {
2202
- if (isPlainObject3(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2503
+ if (isPlainObject4(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2203
2504
  const target = arg.target;
2204
2505
  if (target.type === "checkbox") return target.checked;
2205
2506
  if (target.type === "number" || target.type === "range") {
@@ -2211,14 +2512,6 @@ function eventValue(arg) {
2211
2512
  }
2212
2513
  return arg;
2213
2514
  }
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
2515
  function toIndex(v) {
2223
2516
  if (typeof v === "number") return Number.isInteger(v) ? v : null;
2224
2517
  if (typeof v === "string" && /^-?\d+$/.test(v.trim())) return Number(v.trim());
@@ -2317,7 +2610,7 @@ function childrenOf(node) {
2317
2610
  }
2318
2611
  function renderChild(child, index, scope, engine) {
2319
2612
  if (typeof child === "string") return child;
2320
- if (!isPlainObject3(child)) {
2613
+ if (!isPlainObject4(child)) {
2321
2614
  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
2615
  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
2616
  }
@@ -2332,11 +2625,11 @@ function SduiNodeRenderer({
2332
2625
  engine
2333
2626
  }) {
2334
2627
  const { registry, resolveValue, dispatch, resolveAsset } = engine;
2335
- const slots = isPlainObject3(node.slots) ? node.slots : void 0;
2628
+ const slots = isPlainObject4(node.slots) ? node.slots : void 0;
2336
2629
  const childList = childrenOf(node);
2337
2630
  if (node.component === "ForEach") {
2338
2631
  const source = resolveValue(
2339
- isPlainObject3(node.props) ? node.props.source ?? null : null,
2632
+ isPlainObject4(node.props) ? node.props.source ?? null : null,
2340
2633
  scope
2341
2634
  );
2342
2635
  const rows = Array.isArray(source) ? source : [];
@@ -2346,7 +2639,7 @@ function SduiNodeRenderer({
2346
2639
  }
2347
2640
  return /* @__PURE__ */ jsx(Fragment, { children: rows.map((item, rowIndex) => {
2348
2641
  const rowScope = [...scope, { item, index: rowIndex }];
2349
- const rowKey = isPlainObject3(item) && typeof item.id === "string" ? item.id : rowIndex;
2642
+ const rowKey = isPlainObject4(item) && typeof item.id === "string" ? item.id : rowIndex;
2350
2643
  return /* @__PURE__ */ jsx(React11.Fragment, { children: childList?.map((child, i) => renderChild(child, i, rowScope, engine)) }, rowKey);
2351
2644
  }) });
2352
2645
  }
@@ -2365,7 +2658,7 @@ function SduiNodeRenderer({
2365
2658
  ] });
2366
2659
  }
2367
2660
  const props = {};
2368
- for (const [name, raw] of Object.entries(isPlainObject3(node.props) ? node.props : {})) {
2661
+ for (const [name, raw] of Object.entries(isPlainObject4(node.props) ? node.props : {})) {
2369
2662
  if (DENIED_PROPS2.has(name)) {
2370
2663
  warnOnce2(`prop:${name}`, `[sdui] \uC2A4\uD399\uC5D0\uC11C \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD504\uB86D: ${name} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2371
2664
  continue;
@@ -2404,7 +2697,7 @@ function SduiNodeRenderer({
2404
2697
  if (childLayout) {
2405
2698
  for (const name of CHILD_LAYOUT_PROPS) delete props[name];
2406
2699
  if (childLayout.style) {
2407
- props.style = isPlainObject3(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2700
+ props.style = isPlainObject4(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2408
2701
  }
2409
2702
  if (childLayout.className) {
2410
2703
  props.className = typeof props.className === "string" && props.className ? `${childLayout.className} ${props.className}` : childLayout.className;
@@ -2428,7 +2721,7 @@ var FRAGMENT_MAX_DEPTH = 4;
2428
2721
  function SduiFragment({ node, scope, engine }) {
2429
2722
  const adapter = useSduiAdapter();
2430
2723
  const chain = React11.useContext(SduiFragmentChainContext);
2431
- const raw = isPlainObject3(node.props) ? node.props.id : void 0;
2724
+ const raw = isPlainObject4(node.props) ? node.props.id : void 0;
2432
2725
  const id = engine.resolveValue(raw, scope);
2433
2726
  if (typeof id !== "string" || id === "") {
2434
2727
  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 +2741,8 @@ function SduiFragment({ node, scope, engine }) {
2448
2741
  );
2449
2742
  return null;
2450
2743
  }
2451
- const rawArgs = isPlainObject3(node.props) ? node.props.args : void 0;
2452
- if (rawArgs !== void 0 && !isPlainObject3(rawArgs)) {
2744
+ const rawArgs = isPlainObject4(node.props) ? node.props.args : void 0;
2745
+ if (rawArgs !== void 0 && !isPlainObject4(rawArgs)) {
2453
2746
  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
2747
  }
2455
2748
  const useSpec = adapter?.useSpec;
@@ -2464,7 +2757,7 @@ function SduiFragment({ node, scope, engine }) {
2464
2757
  SduiFragmentBody,
2465
2758
  {
2466
2759
  id,
2467
- args: isPlainObject3(rawArgs) ? rawArgs : void 0,
2760
+ args: isPlainObject4(rawArgs) ? rawArgs : void 0,
2468
2761
  useSpec,
2469
2762
  chain,
2470
2763
  scope,
@@ -2485,13 +2778,13 @@ function SduiFragmentBody({
2485
2778
  const nextChain = React11.useMemo(() => [...chain, id], [chain, id]);
2486
2779
  const bodyScope = React11.useMemo(() => [...scope, { args, callSiteScope: scope }], [scope, args]);
2487
2780
  React11.useEffect(() => {
2488
- if (!DEV || !isPlainObject3(spec) || !Array.isArray(spec.body)) return;
2781
+ if (!DEV || !isPlainObject4(spec) || !Array.isArray(spec.body)) return;
2489
2782
  validateSpec(spec, new Set(Object.keys(engine.registry))).forEach(
2490
2783
  (issue) => console.warn(`[sdui:spec:${id}] ${issue.path} \u2014 ${issue.message}`)
2491
2784
  );
2492
2785
  }, [spec, engine.registry, id]);
2493
2786
  if (spec === void 0) return null;
2494
- if (spec === null || !isPlainObject3(spec) || !Array.isArray(spec.body)) {
2787
+ if (spec === null || !isPlainObject4(spec) || !Array.isArray(spec.body)) {
2495
2788
  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
2789
  return null;
2497
2790
  }
@@ -2621,7 +2914,7 @@ function SduiRenderer({
2621
2914
  if (head in current) {
2622
2915
  let value = current[head];
2623
2916
  for (const segment of segments.slice(cut)) {
2624
- if (!isPlainObject3(value)) return void 0;
2917
+ if (!isPlainObject4(value)) return void 0;
2625
2918
  value = value[segment];
2626
2919
  }
2627
2920
  return value;
@@ -2639,7 +2932,7 @@ function SduiRenderer({
2639
2932
  const frame = nearestItemFrame(scope);
2640
2933
  if (!frame) return null;
2641
2934
  if (value.$item === true) return frame.item;
2642
- return isPlainObject3(frame.item) ? frame.item[value.$item] ?? null : null;
2935
+ return isPlainObject4(frame.item) ? frame.item[value.$item] ?? null : null;
2643
2936
  }
2644
2937
  if (isIndexRef(value)) {
2645
2938
  return nearestItemFrame(scope)?.index ?? null;
@@ -2653,64 +2946,17 @@ function SduiRenderer({
2653
2946
  return void 0;
2654
2947
  }
2655
2948
  if (isCalcRef(value)) {
2656
- const { op: rawOp, args, field: rawField } = value.$calc;
2657
- const op = typeof rawOp === "string" ? rawOp : "";
2658
- if (!Array.isArray(args)) {
2949
+ const calc = value.$calc;
2950
+ const op = typeof calc.op === "string" ? calc.op : "";
2951
+ if (!Array.isArray(calc.args)) {
2659
2952
  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
2953
  return null;
2661
2954
  }
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
- }
2955
+ const resolved = calc.args.map((arg) => resolveValue(arg, scope));
2956
+ return evaluateCalc(calc, resolved, (late) => resolveValue(late, scope));
2711
2957
  }
2712
2958
  if (Array.isArray(value)) return value.map((v) => resolveValue(v, scope));
2713
- if (isPlainObject3(value)) {
2959
+ if (isPlainObject4(value)) {
2714
2960
  const out = {};
2715
2961
  for (const [k, v] of Object.entries(value)) {
2716
2962
  out[k] = deferChainEntry(value, k) ? v : resolveValue(v, scope);
@@ -2879,7 +3125,7 @@ function SduiRenderer({
2879
3125
  break;
2880
3126
  }
2881
3127
  const row = current[index];
2882
- if (!isPlainObject3(row)) break;
3128
+ if (!isPlainObject4(row)) break;
2883
3129
  const nextValue = (action.fromEvent ? eventValue(arg) : action.value) ?? null;
2884
3130
  const next = [...current];
2885
3131
  next[index] = { ...row, [action.field]: nextValue };
@@ -3022,4 +3268,4 @@ function SduiRenderer({
3022
3268
  );
3023
3269
  }
3024
3270
 
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 };
3271
+ 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 };