@wipco/sdui 0.43.0 → 0.45.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--y5jCOV5.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--y5jCOV5.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
@@ -1863,13 +1863,16 @@ function evaluateCalc(calc, resolved, resolve) {
1863
1863
  /* ── 목록·객체 ── */
1864
1864
  case "get": {
1865
1865
  const [container, rawKey] = resolved;
1866
- const key = rawKey == null ? "" : String(rawKey);
1867
1866
  if (Array.isArray(container)) {
1868
- const index = Number(key);
1867
+ if (typeof rawKey !== "number" && (typeof rawKey !== "string" || rawKey.trim() === "")) {
1868
+ return null;
1869
+ }
1870
+ const index = Number(rawKey);
1869
1871
  if (!Number.isInteger(index) || index < 0 || index >= container.length) return null;
1870
1872
  return container[index] ?? null;
1871
1873
  }
1872
1874
  if (isPlainObject(container)) {
1875
+ const key = rawKey == null ? "" : String(rawKey);
1873
1876
  return Object.hasOwn(container, key) ? container[key] ?? null : null;
1874
1877
  }
1875
1878
  return null;
@@ -2014,6 +2017,84 @@ async function runRequestAction(action, deps) {
2014
2017
  }
2015
2018
  }
2016
2019
 
2020
+ // src/state-path.ts
2021
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2022
+ function isPlainObject2(value) {
2023
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2024
+ }
2025
+ function normalizeSegment(raw) {
2026
+ if (typeof raw === "number") {
2027
+ return Number.isInteger(raw) && raw >= 0 ? raw : null;
2028
+ }
2029
+ if (typeof raw !== "string" || raw.trim() === "") return null;
2030
+ if (FORBIDDEN_KEYS.has(raw)) return null;
2031
+ const asNumber = Number(raw);
2032
+ if (Number.isInteger(asNumber) && asNumber >= 0 && String(asNumber) === raw) return asNumber;
2033
+ return raw;
2034
+ }
2035
+ function setAtPath(root, path, value) {
2036
+ if (path.length === 0) return { ok: true, next: value };
2037
+ const segments = [];
2038
+ for (const raw of path) {
2039
+ const segment = normalizeSegment(raw);
2040
+ if (segment === null) {
2041
+ return {
2042
+ ok: false,
2043
+ 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)`
2044
+ };
2045
+ }
2046
+ segments.push(segment);
2047
+ }
2048
+ const write = (container, depth) => {
2049
+ const segment = segments[depth];
2050
+ const last = depth === segments.length - 1;
2051
+ if (typeof segment === "number") {
2052
+ if (!Array.isArray(container)) {
2053
+ return {
2054
+ ok: false,
2055
+ 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`
2056
+ };
2057
+ }
2058
+ if (segment >= container.length) {
2059
+ return {
2060
+ ok: false,
2061
+ 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`
2062
+ };
2063
+ }
2064
+ const nextChild2 = last ? { ok: true, next: value } : write(container[segment] ?? null, depth + 1);
2065
+ if (!nextChild2.ok) return nextChild2;
2066
+ const copy = container.slice();
2067
+ copy[segment] = nextChild2.next;
2068
+ return { ok: true, next: copy };
2069
+ }
2070
+ if (Array.isArray(container)) {
2071
+ return {
2072
+ ok: false,
2073
+ reason: `${describe(segments, depth)}: \uBC30\uC5F4\uC5D0 \uBB38\uC790\uC5F4 \uD0A4 '${segment}'\uB85C \uB4E4\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`
2074
+ };
2075
+ }
2076
+ if (container !== null && container !== void 0 && !isPlainObject2(container)) {
2077
+ return {
2078
+ ok: false,
2079
+ reason: `${describe(segments, depth)}: \uAC1D\uCCB4\uAC00 \uC544\uB2CC \uAC12(${typeof container}) \uC548\uC73C\uB85C \uB4E4\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`
2080
+ };
2081
+ }
2082
+ const base = isPlainObject2(container) ? container : {};
2083
+ const nextChild = last ? { ok: true, next: value } : write(base[segment] ?? null, depth + 1);
2084
+ if (!nextChild.ok) return nextChild;
2085
+ return { ok: true, next: { ...base, [segment]: nextChild.next } };
2086
+ };
2087
+ return write(root, 0);
2088
+ }
2089
+ function describe(segments, upto) {
2090
+ let out = "";
2091
+ for (let i = 0; i <= upto; i += 1) {
2092
+ const segment = segments[i];
2093
+ out += typeof segment === "number" ? `[${segment}]` : out === "" ? String(segment) : `.${segment}`;
2094
+ }
2095
+ return out;
2096
+ }
2097
+
2017
2098
  // src/validate.ts
2018
2099
  var ACTION_NAMES = /* @__PURE__ */ new Set([
2019
2100
  "navigate",
@@ -2052,7 +2133,7 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
2052
2133
  "constructor",
2053
2134
  "prototype"
2054
2135
  ]);
2055
- function isPlainObject2(v) {
2136
+ function isPlainObject3(v) {
2056
2137
  return typeof v === "object" && v !== null && !Array.isArray(v);
2057
2138
  }
2058
2139
  function checkUrlValue(key, value, path, issues) {
@@ -2062,7 +2143,7 @@ function checkUrlValue(key, value, path, issues) {
2062
2143
  }
2063
2144
  function validateSpec(page, knownComponents, slotNamesOf) {
2064
2145
  const issues = [];
2065
- if (!isPlainObject2(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2146
+ if (!isPlainObject3(page)) return [{ path: "$", message: "\uC2A4\uD399\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4" }];
2066
2147
  if (page.version !== 1) issues.push({ path: "$.version", message: "version\uC740 1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
2067
2148
  validatePageData(page.data, issues);
2068
2149
  if (!Array.isArray(page.body)) {
@@ -2076,7 +2157,7 @@ function validateSpec(page, knownComponents, slotNamesOf) {
2076
2157
  }
2077
2158
  function validatePageData(data, issues) {
2078
2159
  if (data === void 0) return;
2079
- if (!isPlainObject2(data)) {
2160
+ if (!isPlainObject3(data)) {
2080
2161
  issues.push({ path: "$.data", message: "data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2081
2162
  return;
2082
2163
  }
@@ -2086,7 +2167,7 @@ function validatePageData(data, issues) {
2086
2167
  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" });
2087
2168
  continue;
2088
2169
  }
2089
- if (!isPlainObject2(raw)) {
2170
+ if (!isPlainObject3(raw)) {
2090
2171
  issues.push({ path, message: "\uC870\uD68C\uB294 { endpoint, select?, when?, fallback? } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2091
2172
  continue;
2092
2173
  }
@@ -2098,7 +2179,7 @@ function validatePageData(data, issues) {
2098
2179
  } else if (!raw.endpoint.startsWith("/")) {
2099
2180
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 /\uB85C \uC2DC\uC791\uD558\uB294 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2100
2181
  }
2101
- } else if (!isPlainObject2(raw.endpoint)) {
2182
+ } else if (!isPlainObject3(raw.endpoint)) {
2102
2183
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uAC12 \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2103
2184
  } else {
2104
2185
  walkValue(raw.endpoint, `${path}.endpoint`, issues, false);
@@ -2121,7 +2202,7 @@ function validatePageData(data, issues) {
2121
2202
  }
2122
2203
  function walkChild(child, path, issues, known, slotNamesOf) {
2123
2204
  if (typeof child === "string") return;
2124
- if (!isPlainObject2(child)) {
2205
+ if (!isPlainObject3(child)) {
2125
2206
  issues.push({ path, message: "children \uD56D\uBAA9\uC740 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2126
2207
  return;
2127
2208
  }
@@ -2133,7 +2214,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2133
2214
  if (known && !RENDERER_BUILTINS.has(node.component) && !known.has(node.component)) {
2134
2215
  issues.push({ path, message: `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uCEF4\uD3EC\uB10C\uD2B8: ${node.component}` });
2135
2216
  }
2136
- if (node.props !== void 0 && !isPlainObject2(node.props)) {
2217
+ if (node.props !== void 0 && !isPlainObject3(node.props)) {
2137
2218
  issues.push({ path: `${path}.props`, message: "props\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2138
2219
  }
2139
2220
  for (const [name, value] of Object.entries(node.props ?? {})) {
@@ -2147,9 +2228,9 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2147
2228
  checkUrlValue(name, value, `${path}.props.${name}`, issues);
2148
2229
  walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
2149
2230
  }
2150
- if (node.component === "Form" && isPlainObject2(node.props) && node.props.rules !== void 0) {
2231
+ if (node.component === "Form" && isPlainObject3(node.props) && node.props.rules !== void 0) {
2151
2232
  const rules = node.props.rules;
2152
- if (!isPlainObject2(rules)) {
2233
+ if (!isPlainObject3(rules)) {
2153
2234
  issues.push({ path: `${path}.props.rules`, message: "rules\uB294 { \uD544\uB4DC: \uADDC\uCE59\uBC30\uC5F4 } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2154
2235
  } else {
2155
2236
  for (const [field, list] of Object.entries(rules)) {
@@ -2160,15 +2241,15 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2160
2241
  }
2161
2242
  }
2162
2243
  if (node.component === "Fragment") {
2163
- const id = isPlainObject2(node.props) ? node.props.id : void 0;
2164
- if ((typeof id !== "string" || id === "") && !isPlainObject2(id)) {
2244
+ const id = isPlainObject3(node.props) ? node.props.id : void 0;
2245
+ if ((typeof id !== "string" || id === "") && !isPlainObject3(id)) {
2165
2246
  issues.push({
2166
2247
  path: `${path}.props.id`,
2167
2248
  message: "Fragment.id\uB294 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uBB38\uC790\uC5F4(\uB610\uB294 \uAC12 \uCC38\uC870)\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4"
2168
2249
  });
2169
2250
  }
2170
- const args = isPlainObject2(node.props) ? node.props.args : void 0;
2171
- if (args !== void 0 && !isPlainObject2(args)) {
2251
+ const args = isPlainObject3(node.props) ? node.props.args : void 0;
2252
+ if (args !== void 0 && !isPlainObject3(args)) {
2172
2253
  issues.push({
2173
2254
  path: `${path}.props.args`,
2174
2255
  message: "Fragment.args\uB294 { \uC774\uB984: \uAC12 } \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4"
@@ -2182,7 +2263,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2182
2263
  }
2183
2264
  }
2184
2265
  if (node.slots !== void 0) {
2185
- if (!isPlainObject2(node.slots)) {
2266
+ if (!isPlainObject3(node.slots)) {
2186
2267
  issues.push({ path: `${path}.slots`, message: "slots\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2187
2268
  } else {
2188
2269
  const slotNames = slotNamesOf?.(node.component) ?? null;
@@ -2218,7 +2299,7 @@ function walkValue(value, path, issues, actionEligible) {
2218
2299
  value.forEach((v, i) => walkValue(v, `${path}[${i}]`, issues, actionEligible));
2219
2300
  return;
2220
2301
  }
2221
- if (!isPlainObject2(value)) return;
2302
+ if (!isPlainObject3(value)) return;
2222
2303
  if (typeof value.action === "string" && (actionEligible || ACTION_NAMES.has(value.action))) {
2223
2304
  if (actionEligible && !ACTION_NAMES.has(value.action)) {
2224
2305
  issues.push({ path, message: `\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${value.action}` });
@@ -2245,7 +2326,7 @@ function walkValue(value, path, issues, actionEligible) {
2245
2326
  }
2246
2327
  if ("$calc" in value) {
2247
2328
  const calc = value.$calc;
2248
- if (!isPlainObject2(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2329
+ if (!isPlainObject3(calc) || typeof calc.op !== "string" || !CALC_OPS.has(calc.op)) {
2249
2330
  issues.push({ path, message: "$calc.op\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
2250
2331
  } else if (!Array.isArray(calc.args)) {
2251
2332
  issues.push({ path: `${path}.$calc.args`, message: "args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
@@ -2274,12 +2355,12 @@ function walkValue(value, path, issues, actionEligible) {
2274
2355
  }
2275
2356
 
2276
2357
  // src/page-data.ts
2277
- function isPlainObject3(value) {
2358
+ function isPlainObject4(value) {
2278
2359
  return typeof value === "object" && value !== null && !Array.isArray(value);
2279
2360
  }
2280
2361
  function planPageData(data, resolve) {
2281
2362
  if (data === void 0) return [];
2282
- if (!isPlainObject3(data)) {
2363
+ if (!isPlainObject4(data)) {
2283
2364
  warnOnce("page-data:root", "[sdui] page.data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4");
2284
2365
  return [];
2285
2366
  }
@@ -2289,7 +2370,7 @@ function planPageData(data, resolve) {
2289
2370
  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`);
2290
2371
  continue;
2291
2372
  }
2292
- if (!isPlainObject3(raw)) {
2373
+ if (!isPlainObject4(raw)) {
2293
2374
  warnOnce(`page-data:${key}`, `[sdui] page.data.${key}\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2294
2375
  continue;
2295
2376
  }
@@ -2324,7 +2405,7 @@ function pickPath(source, path) {
2324
2405
  if (path === void 0 || path === "") return source;
2325
2406
  let value = source;
2326
2407
  for (const segment of path.split(".")) {
2327
- if (!isPlainObject3(value)) return void 0;
2408
+ if (!isPlainObject4(value)) return void 0;
2328
2409
  value = value[segment];
2329
2410
  }
2330
2411
  return value;
@@ -2355,26 +2436,26 @@ function nearestArgsFrame(scope) {
2355
2436
  }
2356
2437
  return void 0;
2357
2438
  }
2358
- function isPlainObject4(v) {
2439
+ function isPlainObject5(v) {
2359
2440
  return typeof v === "object" && v !== null && !Array.isArray(v);
2360
2441
  }
2361
2442
  function isStateRef(v) {
2362
- return isPlainObject4(v) && typeof v.$state === "string";
2443
+ return isPlainObject5(v) && typeof v.$state === "string";
2363
2444
  }
2364
2445
  function isItemRef(v) {
2365
- return isPlainObject4(v) && "$item" in v;
2446
+ return isPlainObject5(v) && "$item" in v;
2366
2447
  }
2367
2448
  function isIndexRef(v) {
2368
- return isPlainObject4(v) && "$index" in v;
2449
+ return isPlainObject5(v) && "$index" in v;
2369
2450
  }
2370
2451
  function isCalcRef(v) {
2371
- return isPlainObject4(v) && isPlainObject4(v.$calc);
2452
+ return isPlainObject5(v) && isPlainObject5(v.$calc);
2372
2453
  }
2373
2454
  function isArgRef(v) {
2374
- return isPlainObject4(v) && typeof v.$arg === "string";
2455
+ return isPlainObject5(v) && typeof v.$arg === "string";
2375
2456
  }
2376
2457
  function isAction(v) {
2377
- return isPlainObject4(v) && typeof v.action === "string";
2458
+ return isPlainObject5(v) && typeof v.action === "string";
2378
2459
  }
2379
2460
  function isActionList(v) {
2380
2461
  return Array.isArray(v) && v.length > 0 && v.every(isAction);
@@ -2398,7 +2479,7 @@ function expandActionEntries(raw, scope) {
2398
2479
  return [];
2399
2480
  }
2400
2481
  function eventValue(arg) {
2401
- if (isPlainObject4(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2482
+ if (isPlainObject5(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2402
2483
  const target = arg.target;
2403
2484
  if (target.type === "checkbox") return target.checked;
2404
2485
  if (target.type === "number" || target.type === "range") {
@@ -2508,7 +2589,7 @@ function childrenOf(node) {
2508
2589
  }
2509
2590
  function renderChild(child, index, scope, engine) {
2510
2591
  if (typeof child === "string") return child;
2511
- if (!isPlainObject4(child)) {
2592
+ if (!isPlainObject5(child)) {
2512
2593
  warnOnce2(`child:${String(child)}`, `[sdui] \uC798\uBABB\uB41C \uB178\uB4DC(\uBB38\uC790\uC5F4 \uB610\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4): ${String(child)}`);
2513
2594
  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);
2514
2595
  }
@@ -2523,11 +2604,11 @@ function SduiNodeRenderer({
2523
2604
  engine
2524
2605
  }) {
2525
2606
  const { registry, resolveValue, dispatch, resolveAsset } = engine;
2526
- const slots = isPlainObject4(node.slots) ? node.slots : void 0;
2607
+ const slots = isPlainObject5(node.slots) ? node.slots : void 0;
2527
2608
  const childList = childrenOf(node);
2528
2609
  if (node.component === "ForEach") {
2529
2610
  const source = resolveValue(
2530
- isPlainObject4(node.props) ? node.props.source ?? null : null,
2611
+ isPlainObject5(node.props) ? node.props.source ?? null : null,
2531
2612
  scope
2532
2613
  );
2533
2614
  const rows = Array.isArray(source) ? source : [];
@@ -2537,7 +2618,7 @@ function SduiNodeRenderer({
2537
2618
  }
2538
2619
  return /* @__PURE__ */ jsx(Fragment, { children: rows.map((item, rowIndex) => {
2539
2620
  const rowScope = [...scope, { item, index: rowIndex }];
2540
- const rowKey = isPlainObject4(item) && typeof item.id === "string" ? item.id : rowIndex;
2621
+ const rowKey = isPlainObject5(item) && typeof item.id === "string" ? item.id : rowIndex;
2541
2622
  return /* @__PURE__ */ jsx(React9.Fragment, { children: childList?.map((child, i) => renderChild(child, i, rowScope, engine)) }, rowKey);
2542
2623
  }) });
2543
2624
  }
@@ -2556,7 +2637,7 @@ function SduiNodeRenderer({
2556
2637
  ] });
2557
2638
  }
2558
2639
  const props = {};
2559
- for (const [name, raw] of Object.entries(isPlainObject4(node.props) ? node.props : {})) {
2640
+ for (const [name, raw] of Object.entries(isPlainObject5(node.props) ? node.props : {})) {
2560
2641
  if (DENIED_PROPS2.has(name)) {
2561
2642
  warnOnce2(`prop:${name}`, `[sdui] \uC2A4\uD399\uC5D0\uC11C \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD504\uB86D: ${name} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2562
2643
  continue;
@@ -2595,7 +2676,7 @@ function SduiNodeRenderer({
2595
2676
  if (childLayout) {
2596
2677
  for (const name of CHILD_LAYOUT_PROPS) delete props[name];
2597
2678
  if (childLayout.style) {
2598
- props.style = isPlainObject4(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2679
+ props.style = isPlainObject5(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2599
2680
  }
2600
2681
  if (childLayout.className) {
2601
2682
  props.className = typeof props.className === "string" && props.className ? `${childLayout.className} ${props.className}` : childLayout.className;
@@ -2619,7 +2700,7 @@ var FRAGMENT_MAX_DEPTH = 4;
2619
2700
  function SduiFragment({ node, scope, engine }) {
2620
2701
  const adapter = useSduiAdapter();
2621
2702
  const chain = React9.useContext(SduiFragmentChainContext);
2622
- const raw = isPlainObject4(node.props) ? node.props.id : void 0;
2703
+ const raw = isPlainObject5(node.props) ? node.props.id : void 0;
2623
2704
  const id = engine.resolveValue(raw, scope);
2624
2705
  if (typeof id !== "string" || id === "") {
2625
2706
  warnOnce2("fragment:id", "[sdui] <Fragment>\uC5D0 \uBB38\uC790\uC5F4 id\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB80C\uB354\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
@@ -2639,8 +2720,8 @@ function SduiFragment({ node, scope, engine }) {
2639
2720
  );
2640
2721
  return null;
2641
2722
  }
2642
- const rawArgs = isPlainObject4(node.props) ? node.props.args : void 0;
2643
- if (rawArgs !== void 0 && !isPlainObject4(rawArgs)) {
2723
+ const rawArgs = isPlainObject5(node.props) ? node.props.args : void 0;
2724
+ if (rawArgs !== void 0 && !isPlainObject5(rawArgs)) {
2644
2725
  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`);
2645
2726
  }
2646
2727
  const useSpec = adapter?.useSpec;
@@ -2655,7 +2736,7 @@ function SduiFragment({ node, scope, engine }) {
2655
2736
  SduiFragmentBody,
2656
2737
  {
2657
2738
  id,
2658
- args: isPlainObject4(rawArgs) ? rawArgs : void 0,
2739
+ args: isPlainObject5(rawArgs) ? rawArgs : void 0,
2659
2740
  useSpec,
2660
2741
  chain,
2661
2742
  scope,
@@ -2676,13 +2757,13 @@ function SduiFragmentBody({
2676
2757
  const nextChain = React9.useMemo(() => [...chain, id], [chain, id]);
2677
2758
  const bodyScope = React9.useMemo(() => [...scope, { args, callSiteScope: scope }], [scope, args]);
2678
2759
  React9.useEffect(() => {
2679
- if (!DEV || !isPlainObject4(spec) || !Array.isArray(spec.body)) return;
2760
+ if (!DEV || !isPlainObject5(spec) || !Array.isArray(spec.body)) return;
2680
2761
  validateSpec(spec, new Set(Object.keys(engine.registry))).forEach(
2681
2762
  (issue) => console.warn(`[sdui:spec:${id}] ${issue.path} \u2014 ${issue.message}`)
2682
2763
  );
2683
2764
  }, [spec, engine.registry, id]);
2684
2765
  if (spec === void 0) return null;
2685
- if (spec === null || !isPlainObject4(spec) || !Array.isArray(spec.body)) {
2766
+ if (spec === null || !isPlainObject5(spec) || !Array.isArray(spec.body)) {
2686
2767
  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`);
2687
2768
  return null;
2688
2769
  }
@@ -2812,7 +2893,7 @@ function SduiRenderer({
2812
2893
  if (head in current) {
2813
2894
  let value = current[head];
2814
2895
  for (const segment of segments.slice(cut)) {
2815
- if (!isPlainObject4(value)) return void 0;
2896
+ if (!isPlainObject5(value)) return void 0;
2816
2897
  value = value[segment];
2817
2898
  }
2818
2899
  return value;
@@ -2830,7 +2911,7 @@ function SduiRenderer({
2830
2911
  const frame = nearestItemFrame(scope);
2831
2912
  if (!frame) return null;
2832
2913
  if (value.$item === true) return frame.item;
2833
- return isPlainObject4(frame.item) ? frame.item[value.$item] ?? null : null;
2914
+ return isPlainObject5(frame.item) ? frame.item[value.$item] ?? null : null;
2834
2915
  }
2835
2916
  if (isIndexRef(value)) {
2836
2917
  return nearestItemFrame(scope)?.index ?? null;
@@ -2854,7 +2935,7 @@ function SduiRenderer({
2854
2935
  return evaluateCalc(calc, resolved, (late) => resolveValue(late, scope));
2855
2936
  }
2856
2937
  if (Array.isArray(value)) return value.map((v) => resolveValue(v, scope));
2857
- if (isPlainObject4(value)) {
2938
+ if (isPlainObject5(value)) {
2858
2939
  const out = {};
2859
2940
  for (const [k, v] of Object.entries(value)) {
2860
2941
  out[k] = deferChainEntry(value, k) ? v : resolveValue(v, scope);
@@ -2962,9 +3043,27 @@ function SduiRenderer({
2962
3043
  }
2963
3044
  hostRef.current.navigate(action.to);
2964
3045
  break;
2965
- case "setState":
2966
- setEntries({ [action.key]: action.fromEvent ? eventValue(arg) : action.value });
3046
+ case "setState": {
3047
+ const nextValue = action.fromEvent ? eventValue(arg) : action.value;
3048
+ if (!Array.isArray(action.path) || action.path.length === 0) {
3049
+ setEntries({ [action.key]: nextValue });
3050
+ break;
3051
+ }
3052
+ const written = setAtPath(
3053
+ stateRef.current[action.key],
3054
+ action.path,
3055
+ nextValue
3056
+ );
3057
+ if (!written.ok) {
3058
+ warnOnce2(
3059
+ `setState:${action.key}:${written.reason}`,
3060
+ `[sdui] setState(${action.key}) \uACBD\uB85C \uC4F0\uAE30\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 \u2014 ${written.reason}`
3061
+ );
3062
+ break;
3063
+ }
3064
+ setEntries({ [action.key]: written.next });
2967
3065
  break;
3066
+ }
2968
3067
  case "open":
2969
3068
  setEntries({ [action.key]: true });
2970
3069
  break;
@@ -3023,7 +3122,7 @@ function SduiRenderer({
3023
3122
  break;
3024
3123
  }
3025
3124
  const row = current[index];
3026
- if (!isPlainObject4(row)) break;
3125
+ if (!isPlainObject5(row)) break;
3027
3126
  const nextValue = (action.fromEvent ? eventValue(arg) : action.value) ?? null;
3028
3127
  const next = [...current];
3029
3128
  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--y5jCOV5.js';
2
2
 
3
3
  /**
4
4
  * 시각 빌더용 컴포넌트 메타데이터 스키마.
@@ -175,6 +175,22 @@ type SduiAction = {
175
175
  } | {
176
176
  action: 'setState';
177
177
  key: string;
178
+ /**
179
+ * 상태 항목 **안쪽 자리**. 주면 항목 전체가 아니라 그 자리만 바꾼다.
180
+ *
181
+ * 조각은 객체 키(문자열)나 배열 인덱스(0 이상 정수)이고, 값 참조를 그대로 쓸 수
182
+ * 있다 — 액션 객체 전체가 디스패치 직전에 풀리기 때문이다:
183
+ *
184
+ * ```json
185
+ * { "action": "setState", "key": "gdRules", "fromEvent": true,
186
+ * "path": [{ "$index": true }, "limits", { "$item": true }, "count"] }
187
+ * ```
188
+ *
189
+ * 이것이 없으면 **동적 키**(등급 이름처럼 사용자가 런타임에 타이핑하는 키)와
190
+ * **중첩 쓰기**(`arrayUpdate`는 최상위 배열의 평탄 필드 하나만 바꾼다)를 스펙으로
191
+ * 말할 수 없었다. 규칙과 거절 조건은 `state-path.ts` 머리말에 있다.
192
+ */
193
+ path?: SduiPropValue[];
178
194
  value?: SduiPropValue;
179
195
  /** true면 이벤트 값(input value, 선택된 탭 value, 페이지 번호 등)을 그대로 저장한다. */
180
196
  fromEvent?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.43.0",
3
+ "version": "0.45.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",