@wipco/sdui 0.44.0 → 0.46.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.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-Bx3oingj.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-Bx3oingj.js';
3
+ import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-C6cOUNPz.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-C6cOUNPz.js';
5
5
  import { S as SduiBoxProps, a as SduiContainerLayoutProps, b as SduiColumnsScale, c as SduiGapScale, d as SduiWeightScale, e as SduiTextAlignScale } from './layout-C3nRXMwb.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-C3nRXMwb.js';
7
7
  import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-BK_jsV7i.js';
package/dist/index.js CHANGED
@@ -1953,7 +1953,8 @@ function evaluateCalc(calc, resolved, resolve) {
1953
1953
  }
1954
1954
  case "split": {
1955
1955
  const sep = typeof calc.sep === "string" ? calc.sep : ",";
1956
- const parts = String(first ?? "").split(sep);
1956
+ let parts = String(first ?? "").split(sep);
1957
+ if (calc.trim === true) parts = parts.map((part) => part.trim());
1957
1958
  if (calc.take === void 0) return parts;
1958
1959
  const index = toNumber2(calc.take);
1959
1960
  const at = index < 0 ? parts.length + index : index;
@@ -2017,6 +2018,84 @@ async function runRequestAction(action, deps) {
2017
2018
  }
2018
2019
  }
2019
2020
 
2021
+ // src/state-path.ts
2022
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2023
+ function isPlainObject2(value) {
2024
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2025
+ }
2026
+ function normalizeSegment(raw) {
2027
+ if (typeof raw === "number") {
2028
+ return Number.isInteger(raw) && raw >= 0 ? raw : null;
2029
+ }
2030
+ if (typeof raw !== "string" || raw.trim() === "") return null;
2031
+ if (FORBIDDEN_KEYS.has(raw)) return null;
2032
+ const asNumber = Number(raw);
2033
+ if (Number.isInteger(asNumber) && asNumber >= 0 && String(asNumber) === raw) return asNumber;
2034
+ return raw;
2035
+ }
2036
+ function setAtPath(root, path, value) {
2037
+ if (path.length === 0) return { ok: true, next: value };
2038
+ const segments = [];
2039
+ for (const raw of path) {
2040
+ const segment = normalizeSegment(raw);
2041
+ if (segment === null) {
2042
+ return {
2043
+ ok: false,
2044
+ reason: `\uACBD\uB85C \uC870\uAC01\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${JSON.stringify(raw)} (\uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uBB38\uC790\uC5F4\uC774\uB098 0 \uC774\uC0C1 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4)`
2045
+ };
2046
+ }
2047
+ segments.push(segment);
2048
+ }
2049
+ const write = (container, depth) => {
2050
+ const segment = segments[depth];
2051
+ const last = depth === segments.length - 1;
2052
+ if (typeof segment === "number") {
2053
+ if (!Array.isArray(container)) {
2054
+ return {
2055
+ ok: false,
2056
+ reason: `${describe(segments, depth)}: \uBC30\uC5F4\uC774 \uC544\uB2CC \uC790\uB9AC\uC5D0 \uC778\uB371\uC2A4 ${segment}\uB85C \uB4E4\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`
2057
+ };
2058
+ }
2059
+ if (segment >= container.length) {
2060
+ return {
2061
+ ok: false,
2062
+ reason: `${describe(segments, depth)}: \uC778\uB371\uC2A4 ${segment}\uAC00 \uBC94\uC704 \uBC16\uC785\uB2C8\uB2E4(\uAE38\uC774 ${container.length}) \u2014 \uBC30\uC5F4\uC744 \uC778\uB371\uC2A4\uB85C \uB298\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`
2063
+ };
2064
+ }
2065
+ const nextChild2 = last ? { ok: true, next: value } : write(container[segment] ?? null, depth + 1);
2066
+ if (!nextChild2.ok) return nextChild2;
2067
+ const copy = container.slice();
2068
+ copy[segment] = nextChild2.next;
2069
+ return { ok: true, next: copy };
2070
+ }
2071
+ if (Array.isArray(container)) {
2072
+ return {
2073
+ ok: false,
2074
+ reason: `${describe(segments, depth)}: \uBC30\uC5F4\uC5D0 \uBB38\uC790\uC5F4 \uD0A4 '${segment}'\uB85C \uB4E4\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`
2075
+ };
2076
+ }
2077
+ if (container !== null && container !== void 0 && !isPlainObject2(container)) {
2078
+ return {
2079
+ ok: false,
2080
+ reason: `${describe(segments, depth)}: \uAC1D\uCCB4\uAC00 \uC544\uB2CC \uAC12(${typeof container}) \uC548\uC73C\uB85C \uB4E4\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`
2081
+ };
2082
+ }
2083
+ const base = isPlainObject2(container) ? container : {};
2084
+ const nextChild = last ? { ok: true, next: value } : write(base[segment] ?? null, depth + 1);
2085
+ if (!nextChild.ok) return nextChild;
2086
+ return { ok: true, next: { ...base, [segment]: nextChild.next } };
2087
+ };
2088
+ return write(root, 0);
2089
+ }
2090
+ function describe(segments, upto) {
2091
+ let out = "";
2092
+ for (let i = 0; i <= upto; i += 1) {
2093
+ const segment = segments[i];
2094
+ out += typeof segment === "number" ? `[${segment}]` : out === "" ? String(segment) : `.${segment}`;
2095
+ }
2096
+ return out;
2097
+ }
2098
+
2020
2099
  // src/validate.ts
2021
2100
  var ACTION_NAMES = /* @__PURE__ */ new Set([
2022
2101
  "navigate",
@@ -2055,7 +2134,7 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
2055
2134
  "constructor",
2056
2135
  "prototype"
2057
2136
  ]);
2058
- function isPlainObject2(v) {
2137
+ function isPlainObject3(v) {
2059
2138
  return typeof v === "object" && v !== null && !Array.isArray(v);
2060
2139
  }
2061
2140
  function checkUrlValue(key, value, path, issues) {
@@ -2065,7 +2144,7 @@ function checkUrlValue(key, value, path, issues) {
2065
2144
  }
2066
2145
  function validateSpec(page, knownComponents, slotNamesOf) {
2067
2146
  const issues = [];
2068
- if (!isPlainObject2(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2147
+ if (!isPlainObject3(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2069
2148
  if (page.version !== 1) issues.push({ path: "$.version", message: "version\uC740 1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
2070
2149
  validatePageData(page.data, issues);
2071
2150
  if (!Array.isArray(page.body)) {
@@ -2079,7 +2158,7 @@ function validateSpec(page, knownComponents, slotNamesOf) {
2079
2158
  }
2080
2159
  function validatePageData(data, issues) {
2081
2160
  if (data === void 0) return;
2082
- if (!isPlainObject2(data)) {
2161
+ if (!isPlainObject3(data)) {
2083
2162
  issues.push({ path: "$.data", message: "data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2084
2163
  return;
2085
2164
  }
@@ -2089,7 +2168,7 @@ function validatePageData(data, issues) {
2089
2168
  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" });
2090
2169
  continue;
2091
2170
  }
2092
- if (!isPlainObject2(raw)) {
2171
+ if (!isPlainObject3(raw)) {
2093
2172
  issues.push({ path, message: "\uC870\uD68C\uB294 { endpoint, select?, when?, fallback? } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2094
2173
  continue;
2095
2174
  }
@@ -2101,7 +2180,7 @@ function validatePageData(data, issues) {
2101
2180
  } else if (!raw.endpoint.startsWith("/")) {
2102
2181
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 /\uB85C \uC2DC\uC791\uD558\uB294 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2103
2182
  }
2104
- } else if (!isPlainObject2(raw.endpoint)) {
2183
+ } else if (!isPlainObject3(raw.endpoint)) {
2105
2184
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uAC12 \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2106
2185
  } else {
2107
2186
  walkValue(raw.endpoint, `${path}.endpoint`, issues, false);
@@ -2124,7 +2203,7 @@ function validatePageData(data, issues) {
2124
2203
  }
2125
2204
  function walkChild(child, path, issues, known, slotNamesOf) {
2126
2205
  if (typeof child === "string") return;
2127
- if (!isPlainObject2(child)) {
2206
+ if (!isPlainObject3(child)) {
2128
2207
  issues.push({ path, message: "children \uD56D\uBAA9\uC740 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2129
2208
  return;
2130
2209
  }
@@ -2136,7 +2215,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2136
2215
  if (known && !RENDERER_BUILTINS.has(node.component) && !known.has(node.component)) {
2137
2216
  issues.push({ path, message: `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uCEF4\uD3EC\uB10C\uD2B8: ${node.component}` });
2138
2217
  }
2139
- if (node.props !== void 0 && !isPlainObject2(node.props)) {
2218
+ if (node.props !== void 0 && !isPlainObject3(node.props)) {
2140
2219
  issues.push({ path: `${path}.props`, message: "props\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2141
2220
  }
2142
2221
  for (const [name, value] of Object.entries(node.props ?? {})) {
@@ -2150,9 +2229,9 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2150
2229
  checkUrlValue(name, value, `${path}.props.${name}`, issues);
2151
2230
  walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
2152
2231
  }
2153
- if (node.component === "Form" && isPlainObject2(node.props) && node.props.rules !== void 0) {
2232
+ if (node.component === "Form" && isPlainObject3(node.props) && node.props.rules !== void 0) {
2154
2233
  const rules = node.props.rules;
2155
- if (!isPlainObject2(rules)) {
2234
+ if (!isPlainObject3(rules)) {
2156
2235
  issues.push({ path: `${path}.props.rules`, message: "rules\uB294 { \uD544\uB4DC: \uADDC\uCE59\uBC30\uC5F4 } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2157
2236
  } else {
2158
2237
  for (const [field, list] of Object.entries(rules)) {
@@ -2163,15 +2242,15 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2163
2242
  }
2164
2243
  }
2165
2244
  if (node.component === "Fragment") {
2166
- const id = isPlainObject2(node.props) ? node.props.id : void 0;
2167
- if ((typeof id !== "string" || id === "") && !isPlainObject2(id)) {
2245
+ const id = isPlainObject3(node.props) ? node.props.id : void 0;
2246
+ if ((typeof id !== "string" || id === "") && !isPlainObject3(id)) {
2168
2247
  issues.push({
2169
2248
  path: `${path}.props.id`,
2170
2249
  message: "Fragment.id\uB294 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uBB38\uC790\uC5F4(\uB610\uB294 \uAC12 \uCC38\uC870)\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4"
2171
2250
  });
2172
2251
  }
2173
- const args = isPlainObject2(node.props) ? node.props.args : void 0;
2174
- if (args !== void 0 && !isPlainObject2(args)) {
2252
+ const args = isPlainObject3(node.props) ? node.props.args : void 0;
2253
+ if (args !== void 0 && !isPlainObject3(args)) {
2175
2254
  issues.push({
2176
2255
  path: `${path}.props.args`,
2177
2256
  message: "Fragment.args\uB294 { \uC774\uB984: \uAC12 } \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4"
@@ -2185,7 +2264,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2185
2264
  }
2186
2265
  }
2187
2266
  if (node.slots !== void 0) {
2188
- if (!isPlainObject2(node.slots)) {
2267
+ if (!isPlainObject3(node.slots)) {
2189
2268
  issues.push({ path: `${path}.slots`, message: "slots\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2190
2269
  } else {
2191
2270
  const slotNames = slotNamesOf?.(node.component) ?? null;
@@ -2221,7 +2300,7 @@ function walkValue(value, path, issues, actionEligible) {
2221
2300
  value.forEach((v, i) => walkValue(v, `${path}[${i}]`, issues, actionEligible));
2222
2301
  return;
2223
2302
  }
2224
- if (!isPlainObject2(value)) return;
2303
+ if (!isPlainObject3(value)) return;
2225
2304
  if (typeof value.action === "string" && (actionEligible || ACTION_NAMES.has(value.action))) {
2226
2305
  if (actionEligible && !ACTION_NAMES.has(value.action)) {
2227
2306
  issues.push({ path, message: `\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${value.action}` });
@@ -2248,7 +2327,7 @@ function walkValue(value, path, issues, actionEligible) {
2248
2327
  }
2249
2328
  if ("$calc" in value) {
2250
2329
  const calc = value.$calc;
2251
- if (!isPlainObject2(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2330
+ if (!isPlainObject3(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2252
2331
  issues.push({ path, message: "$calc.op\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
2253
2332
  } else if (!Array.isArray(calc.args)) {
2254
2333
  issues.push({ path: `${path}.$calc.args`, message: "args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
@@ -2277,12 +2356,12 @@ function walkValue(value, path, issues, actionEligible) {
2277
2356
  }
2278
2357
 
2279
2358
  // src/page-data.ts
2280
- function isPlainObject3(value) {
2359
+ function isPlainObject4(value) {
2281
2360
  return typeof value === "object" && value !== null && !Array.isArray(value);
2282
2361
  }
2283
2362
  function planPageData(data, resolve) {
2284
2363
  if (data === void 0) return [];
2285
- if (!isPlainObject3(data)) {
2364
+ if (!isPlainObject4(data)) {
2286
2365
  warnOnce("page-data:root", "[sdui] page.data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4");
2287
2366
  return [];
2288
2367
  }
@@ -2292,7 +2371,7 @@ function planPageData(data, resolve) {
2292
2371
  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`);
2293
2372
  continue;
2294
2373
  }
2295
- if (!isPlainObject3(raw)) {
2374
+ if (!isPlainObject4(raw)) {
2296
2375
  warnOnce(`page-data:${key}`, `[sdui] page.data.${key}\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2297
2376
  continue;
2298
2377
  }
@@ -2327,7 +2406,7 @@ function pickPath(source, path) {
2327
2406
  if (path === void 0 || path === "") return source;
2328
2407
  let value = source;
2329
2408
  for (const segment of path.split(".")) {
2330
- if (!isPlainObject3(value)) return void 0;
2409
+ if (!isPlainObject4(value)) return void 0;
2331
2410
  value = value[segment];
2332
2411
  }
2333
2412
  return value;
@@ -2358,26 +2437,26 @@ function nearestArgsFrame(scope) {
2358
2437
  }
2359
2438
  return void 0;
2360
2439
  }
2361
- function isPlainObject4(v) {
2440
+ function isPlainObject5(v) {
2362
2441
  return typeof v === "object" && v !== null && !Array.isArray(v);
2363
2442
  }
2364
2443
  function isStateRef(v) {
2365
- return isPlainObject4(v) && typeof v.$state === "string";
2444
+ return isPlainObject5(v) && typeof v.$state === "string";
2366
2445
  }
2367
2446
  function isItemRef(v) {
2368
- return isPlainObject4(v) && "$item" in v;
2447
+ return isPlainObject5(v) && "$item" in v;
2369
2448
  }
2370
2449
  function isIndexRef(v) {
2371
- return isPlainObject4(v) && "$index" in v;
2450
+ return isPlainObject5(v) && "$index" in v;
2372
2451
  }
2373
2452
  function isCalcRef(v) {
2374
- return isPlainObject4(v) && isPlainObject4(v.$calc);
2453
+ return isPlainObject5(v) && isPlainObject5(v.$calc);
2375
2454
  }
2376
2455
  function isArgRef(v) {
2377
- return isPlainObject4(v) && typeof v.$arg === "string";
2456
+ return isPlainObject5(v) && typeof v.$arg === "string";
2378
2457
  }
2379
2458
  function isAction(v) {
2380
- return isPlainObject4(v) && typeof v.action === "string";
2459
+ return isPlainObject5(v) && typeof v.action === "string";
2381
2460
  }
2382
2461
  function isActionList(v) {
2383
2462
  return Array.isArray(v) && v.length > 0 && v.every(isAction);
@@ -2401,7 +2480,7 @@ function expandActionEntries(raw, scope) {
2401
2480
  return [];
2402
2481
  }
2403
2482
  function eventValue(arg) {
2404
- if (isPlainObject4(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2483
+ if (isPlainObject5(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2405
2484
  const target = arg.target;
2406
2485
  if (target.type === "checkbox") return target.checked;
2407
2486
  if (target.type === "number" || target.type === "range") {
@@ -2511,7 +2590,7 @@ function childrenOf(node) {
2511
2590
  }
2512
2591
  function renderChild(child, index, scope, engine) {
2513
2592
  if (typeof child === "string") return child;
2514
- if (!isPlainObject4(child)) {
2593
+ if (!isPlainObject5(child)) {
2515
2594
  warnOnce2(`child:${String(child)}`, `[sdui] \uC798\uBABB\uB41C \uB178\uB4DC(\uBB38\uC790\uC5F4 \uB610\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4): ${String(child)}`);
2516
2595
  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);
2517
2596
  }
@@ -2526,11 +2605,11 @@ function SduiNodeRenderer({
2526
2605
  engine
2527
2606
  }) {
2528
2607
  const { registry, resolveValue, dispatch, resolveAsset } = engine;
2529
- const slots = isPlainObject4(node.slots) ? node.slots : void 0;
2608
+ const slots = isPlainObject5(node.slots) ? node.slots : void 0;
2530
2609
  const childList = childrenOf(node);
2531
2610
  if (node.component === "ForEach") {
2532
2611
  const source = resolveValue(
2533
- isPlainObject4(node.props) ? node.props.source ?? null : null,
2612
+ isPlainObject5(node.props) ? node.props.source ?? null : null,
2534
2613
  scope
2535
2614
  );
2536
2615
  const rows = Array.isArray(source) ? source : [];
@@ -2540,7 +2619,7 @@ function SduiNodeRenderer({
2540
2619
  }
2541
2620
  return /* @__PURE__ */ jsx(Fragment, { children: rows.map((item, rowIndex) => {
2542
2621
  const rowScope = [...scope, { item, index: rowIndex }];
2543
- const rowKey = isPlainObject4(item) && typeof item.id === "string" ? item.id : rowIndex;
2622
+ const rowKey = isPlainObject5(item) && typeof item.id === "string" ? item.id : rowIndex;
2544
2623
  return /* @__PURE__ */ jsx(React9.Fragment, { children: childList?.map((child, i) => renderChild(child, i, rowScope, engine)) }, rowKey);
2545
2624
  }) });
2546
2625
  }
@@ -2559,7 +2638,7 @@ function SduiNodeRenderer({
2559
2638
  ] });
2560
2639
  }
2561
2640
  const props = {};
2562
- for (const [name, raw] of Object.entries(isPlainObject4(node.props) ? node.props : {})) {
2641
+ for (const [name, raw] of Object.entries(isPlainObject5(node.props) ? node.props : {})) {
2563
2642
  if (DENIED_PROPS2.has(name)) {
2564
2643
  warnOnce2(`prop:${name}`, `[sdui] \uC2A4\uD399\uC5D0\uC11C \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD504\uB86D: ${name} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2565
2644
  continue;
@@ -2598,7 +2677,7 @@ function SduiNodeRenderer({
2598
2677
  if (childLayout) {
2599
2678
  for (const name of CHILD_LAYOUT_PROPS) delete props[name];
2600
2679
  if (childLayout.style) {
2601
- props.style = isPlainObject4(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2680
+ props.style = isPlainObject5(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2602
2681
  }
2603
2682
  if (childLayout.className) {
2604
2683
  props.className = typeof props.className === "string" && props.className ? `${childLayout.className} ${props.className}` : childLayout.className;
@@ -2622,7 +2701,7 @@ var FRAGMENT_MAX_DEPTH = 4;
2622
2701
  function SduiFragment({ node, scope, engine }) {
2623
2702
  const adapter = useSduiAdapter();
2624
2703
  const chain = React9.useContext(SduiFragmentChainContext);
2625
- const raw = isPlainObject4(node.props) ? node.props.id : void 0;
2704
+ const raw = isPlainObject5(node.props) ? node.props.id : void 0;
2626
2705
  const id = engine.resolveValue(raw, scope);
2627
2706
  if (typeof id !== "string" || id === "") {
2628
2707
  warnOnce2("fragment:id", "[sdui] <Fragment>\uC5D0 \uBB38\uC790\uC5F4 id\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB80C\uB354\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
@@ -2642,8 +2721,8 @@ function SduiFragment({ node, scope, engine }) {
2642
2721
  );
2643
2722
  return null;
2644
2723
  }
2645
- const rawArgs = isPlainObject4(node.props) ? node.props.args : void 0;
2646
- if (rawArgs !== void 0 && !isPlainObject4(rawArgs)) {
2724
+ const rawArgs = isPlainObject5(node.props) ? node.props.args : void 0;
2725
+ if (rawArgs !== void 0 && !isPlainObject5(rawArgs)) {
2647
2726
  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`);
2648
2727
  }
2649
2728
  const useSpec = adapter?.useSpec;
@@ -2658,7 +2737,7 @@ function SduiFragment({ node, scope, engine }) {
2658
2737
  SduiFragmentBody,
2659
2738
  {
2660
2739
  id,
2661
- args: isPlainObject4(rawArgs) ? rawArgs : void 0,
2740
+ args: isPlainObject5(rawArgs) ? rawArgs : void 0,
2662
2741
  useSpec,
2663
2742
  chain,
2664
2743
  scope,
@@ -2679,13 +2758,13 @@ function SduiFragmentBody({
2679
2758
  const nextChain = React9.useMemo(() => [...chain, id], [chain, id]);
2680
2759
  const bodyScope = React9.useMemo(() => [...scope, { args, callSiteScope: scope }], [scope, args]);
2681
2760
  React9.useEffect(() => {
2682
- if (!DEV || !isPlainObject4(spec) || !Array.isArray(spec.body)) return;
2761
+ if (!DEV || !isPlainObject5(spec) || !Array.isArray(spec.body)) return;
2683
2762
  validateSpec(spec, new Set(Object.keys(engine.registry))).forEach(
2684
2763
  (issue) => console.warn(`[sdui:spec:${id}] ${issue.path} \u2014 ${issue.message}`)
2685
2764
  );
2686
2765
  }, [spec, engine.registry, id]);
2687
2766
  if (spec === void 0) return null;
2688
- if (spec === null || !isPlainObject4(spec) || !Array.isArray(spec.body)) {
2767
+ if (spec === null || !isPlainObject5(spec) || !Array.isArray(spec.body)) {
2689
2768
  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`);
2690
2769
  return null;
2691
2770
  }
@@ -2815,7 +2894,7 @@ function SduiRenderer({
2815
2894
  if (head in current) {
2816
2895
  let value = current[head];
2817
2896
  for (const segment of segments.slice(cut)) {
2818
- if (!isPlainObject4(value)) return void 0;
2897
+ if (!isPlainObject5(value)) return void 0;
2819
2898
  value = value[segment];
2820
2899
  }
2821
2900
  return value;
@@ -2833,7 +2912,7 @@ function SduiRenderer({
2833
2912
  const frame = nearestItemFrame(scope);
2834
2913
  if (!frame) return null;
2835
2914
  if (value.$item === true) return frame.item;
2836
- return isPlainObject4(frame.item) ? frame.item[value.$item] ?? null : null;
2915
+ return isPlainObject5(frame.item) ? frame.item[value.$item] ?? null : null;
2837
2916
  }
2838
2917
  if (isIndexRef(value)) {
2839
2918
  return nearestItemFrame(scope)?.index ?? null;
@@ -2857,7 +2936,7 @@ function SduiRenderer({
2857
2936
  return evaluateCalc(calc, resolved, (late) => resolveValue(late, scope));
2858
2937
  }
2859
2938
  if (Array.isArray(value)) return value.map((v) => resolveValue(v, scope));
2860
- if (isPlainObject4(value)) {
2939
+ if (isPlainObject5(value)) {
2861
2940
  const out = {};
2862
2941
  for (const [k, v] of Object.entries(value)) {
2863
2942
  out[k] = deferChainEntry(value, k) ? v : resolveValue(v, scope);
@@ -2965,9 +3044,27 @@ function SduiRenderer({
2965
3044
  }
2966
3045
  hostRef.current.navigate(action.to);
2967
3046
  break;
2968
- case "setState":
2969
- setEntries({ [action.key]: action.fromEvent ? eventValue(arg) : action.value });
3047
+ case "setState": {
3048
+ const nextValue = action.fromEvent ? eventValue(arg) : action.value;
3049
+ if (!Array.isArray(action.path) || action.path.length === 0) {
3050
+ setEntries({ [action.key]: nextValue });
3051
+ break;
3052
+ }
3053
+ const written = setAtPath(
3054
+ stateRef.current[action.key],
3055
+ action.path,
3056
+ nextValue
3057
+ );
3058
+ if (!written.ok) {
3059
+ warnOnce2(
3060
+ `setState:${action.key}:${written.reason}`,
3061
+ `[sdui] setState(${action.key}) \uACBD\uB85C \uC4F0\uAE30\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 \u2014 ${written.reason}`
3062
+ );
3063
+ break;
3064
+ }
3065
+ setEntries({ [action.key]: written.next });
2970
3066
  break;
3067
+ }
2971
3068
  case "open":
2972
3069
  setEntries({ [action.key]: true });
2973
3070
  break;
@@ -3026,7 +3123,7 @@ function SduiRenderer({
3026
3123
  break;
3027
3124
  }
3028
3125
  const row = current[index];
3029
- if (!isPlainObject4(row)) break;
3126
+ if (!isPlainObject5(row)) break;
3030
3127
  const nextValue = (action.fromEvent ? eventValue(arg) : action.value) ?? null;
3031
3128
  const next = [...current];
3032
3129
  next[index] = { ...row, [action.field]: nextValue };
package/dist/meta.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { b as SduiPropValue, i as SduiNode } from './types-Bx3oingj.js';
1
+ import { b as SduiPropValue, i as SduiNode } from './types-C6cOUNPz.js';
2
2
 
3
3
  /**
4
4
  * 시각 빌더용 컴포넌트 메타데이터 스키마.
@@ -129,6 +129,11 @@ interface SduiCalcRef {
129
129
  sep?: string;
130
130
  /** `split`에서 꺼낼 조각의 인덱스. 음수는 뒤에서부터 센다 */
131
131
  take?: number;
132
+ /**
133
+ * `split`의 조각에서 앞뒤 공백을 턴다. 사람이 치는 쉼표 목록("S, A, B")이
134
+ * 이 연산의 주된 입력인데, 그 조각이 **키로 쓰이면** 공백 하나가 사고가 된다.
135
+ */
136
+ trim?: boolean;
132
137
  /** `sortBy`를 내림차순으로 */
133
138
  desc?: boolean;
134
139
  };
@@ -175,6 +180,22 @@ type SduiAction = {
175
180
  } | {
176
181
  action: 'setState';
177
182
  key: string;
183
+ /**
184
+ * 상태 항목 **안쪽 자리**. 주면 항목 전체가 아니라 그 자리만 바꾼다.
185
+ *
186
+ * 조각은 객체 키(문자열)나 배열 인덱스(0 이상 정수)이고, 값 참조를 그대로 쓸 수
187
+ * 있다 — 액션 객체 전체가 디스패치 직전에 풀리기 때문이다:
188
+ *
189
+ * ```json
190
+ * { "action": "setState", "key": "gdRules", "fromEvent": true,
191
+ * "path": [{ "$index": true }, "limits", { "$item": true }, "count"] }
192
+ * ```
193
+ *
194
+ * 이것이 없으면 **동적 키**(등급 이름처럼 사용자가 런타임에 타이핑하는 키)와
195
+ * **중첩 쓰기**(`arrayUpdate`는 최상위 배열의 평탄 필드 하나만 바꾼다)를 스펙으로
196
+ * 말할 수 없었다. 규칙과 거절 조건은 `state-path.ts` 머리말에 있다.
197
+ */
198
+ path?: SduiPropValue[];
178
199
  value?: SduiPropValue;
179
200
  /** true면 이벤트 값(input value, 선택된 탭 value, 페이지 번호 등)을 그대로 저장한다. */
180
201
  fromEvent?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",