@wipco/sdui 0.44.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
@@ -2017,6 +2017,84 @@ async function runRequestAction(action, deps) {
2017
2017
  }
2018
2018
  }
2019
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
+
2020
2098
  // src/validate.ts
2021
2099
  var ACTION_NAMES = /* @__PURE__ */ new Set([
2022
2100
  "navigate",
@@ -2055,7 +2133,7 @@ var DENIED_PROPS = /* @__PURE__ */ new Set([
2055
2133
  "constructor",
2056
2134
  "prototype"
2057
2135
  ]);
2058
- function isPlainObject2(v) {
2136
+ function isPlainObject3(v) {
2059
2137
  return typeof v === "object" && v !== null && !Array.isArray(v);
2060
2138
  }
2061
2139
  function checkUrlValue(key, value, path, issues) {
@@ -2065,7 +2143,7 @@ function checkUrlValue(key, value, path, issues) {
2065
2143
  }
2066
2144
  function validateSpec(page, knownComponents, slotNamesOf) {
2067
2145
  const issues = [];
2068
- 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" }];
2069
2147
  if (page.version !== 1) issues.push({ path: "$.version", message: "version\uC740 1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
2070
2148
  validatePageData(page.data, issues);
2071
2149
  if (!Array.isArray(page.body)) {
@@ -2079,7 +2157,7 @@ function validateSpec(page, knownComponents, slotNamesOf) {
2079
2157
  }
2080
2158
  function validatePageData(data, issues) {
2081
2159
  if (data === void 0) return;
2082
- if (!isPlainObject2(data)) {
2160
+ if (!isPlainObject3(data)) {
2083
2161
  issues.push({ path: "$.data", message: "data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2084
2162
  return;
2085
2163
  }
@@ -2089,7 +2167,7 @@ function validatePageData(data, issues) {
2089
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" });
2090
2168
  continue;
2091
2169
  }
2092
- if (!isPlainObject2(raw)) {
2170
+ if (!isPlainObject3(raw)) {
2093
2171
  issues.push({ path, message: "\uC870\uD68C\uB294 { endpoint, select?, when?, fallback? } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2094
2172
  continue;
2095
2173
  }
@@ -2101,7 +2179,7 @@ function validatePageData(data, issues) {
2101
2179
  } else if (!raw.endpoint.startsWith("/")) {
2102
2180
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 /\uB85C \uC2DC\uC791\uD558\uB294 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2103
2181
  }
2104
- } else if (!isPlainObject2(raw.endpoint)) {
2182
+ } else if (!isPlainObject3(raw.endpoint)) {
2105
2183
  issues.push({ path: `${path}.endpoint`, message: "endpoint\uB294 \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uAC12 \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2106
2184
  } else {
2107
2185
  walkValue(raw.endpoint, `${path}.endpoint`, issues, false);
@@ -2124,7 +2202,7 @@ function validatePageData(data, issues) {
2124
2202
  }
2125
2203
  function walkChild(child, path, issues, known, slotNamesOf) {
2126
2204
  if (typeof child === "string") return;
2127
- if (!isPlainObject2(child)) {
2205
+ if (!isPlainObject3(child)) {
2128
2206
  issues.push({ path, message: "children \uD56D\uBAA9\uC740 \uBB38\uC790\uC5F4 \uB610\uB294 \uB178\uB4DC \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2129
2207
  return;
2130
2208
  }
@@ -2136,7 +2214,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2136
2214
  if (known && !RENDERER_BUILTINS.has(node.component) && !known.has(node.component)) {
2137
2215
  issues.push({ path, message: `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uCEF4\uD3EC\uB10C\uD2B8: ${node.component}` });
2138
2216
  }
2139
- if (node.props !== void 0 && !isPlainObject2(node.props)) {
2217
+ if (node.props !== void 0 && !isPlainObject3(node.props)) {
2140
2218
  issues.push({ path: `${path}.props`, message: "props\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2141
2219
  }
2142
2220
  for (const [name, value] of Object.entries(node.props ?? {})) {
@@ -2150,9 +2228,9 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2150
2228
  checkUrlValue(name, value, `${path}.props.${name}`, issues);
2151
2229
  walkValue(value, `${path}.props.${name}`, issues, name.startsWith("on") || name === "rowClick");
2152
2230
  }
2153
- 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) {
2154
2232
  const rules = node.props.rules;
2155
- if (!isPlainObject2(rules)) {
2233
+ if (!isPlainObject3(rules)) {
2156
2234
  issues.push({ path: `${path}.props.rules`, message: "rules\uB294 { \uD544\uB4DC: \uADDC\uCE59\uBC30\uC5F4 } \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2157
2235
  } else {
2158
2236
  for (const [field, list] of Object.entries(rules)) {
@@ -2163,15 +2241,15 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2163
2241
  }
2164
2242
  }
2165
2243
  if (node.component === "Fragment") {
2166
- const id = isPlainObject2(node.props) ? node.props.id : void 0;
2167
- 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)) {
2168
2246
  issues.push({
2169
2247
  path: `${path}.props.id`,
2170
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"
2171
2249
  });
2172
2250
  }
2173
- const args = isPlainObject2(node.props) ? node.props.args : void 0;
2174
- if (args !== void 0 && !isPlainObject2(args)) {
2251
+ const args = isPlainObject3(node.props) ? node.props.args : void 0;
2252
+ if (args !== void 0 && !isPlainObject3(args)) {
2175
2253
  issues.push({
2176
2254
  path: `${path}.props.args`,
2177
2255
  message: "Fragment.args\uB294 { \uC774\uB984: \uAC12 } \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4"
@@ -2185,7 +2263,7 @@ function walkChild(child, path, issues, known, slotNamesOf) {
2185
2263
  }
2186
2264
  }
2187
2265
  if (node.slots !== void 0) {
2188
- if (!isPlainObject2(node.slots)) {
2266
+ if (!isPlainObject3(node.slots)) {
2189
2267
  issues.push({ path: `${path}.slots`, message: "slots\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4" });
2190
2268
  } else {
2191
2269
  const slotNames = slotNamesOf?.(node.component) ?? null;
@@ -2221,7 +2299,7 @@ function walkValue(value, path, issues, actionEligible) {
2221
2299
  value.forEach((v, i) => walkValue(v, `${path}[${i}]`, issues, actionEligible));
2222
2300
  return;
2223
2301
  }
2224
- if (!isPlainObject2(value)) return;
2302
+ if (!isPlainObject3(value)) return;
2225
2303
  if (typeof value.action === "string" && (actionEligible || ACTION_NAMES.has(value.action))) {
2226
2304
  if (actionEligible && !ACTION_NAMES.has(value.action)) {
2227
2305
  issues.push({ path, message: `\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${value.action}` });
@@ -2248,7 +2326,7 @@ function walkValue(value, path, issues, actionEligible) {
2248
2326
  }
2249
2327
  if ("$calc" in value) {
2250
2328
  const calc = value.$calc;
2251
- 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)) {
2252
2330
  issues.push({ path, message: "$calc.op\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
2253
2331
  } else if (!Array.isArray(calc.args)) {
2254
2332
  issues.push({ path: `${path}.$calc.args`, message: "args\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" });
@@ -2277,12 +2355,12 @@ function walkValue(value, path, issues, actionEligible) {
2277
2355
  }
2278
2356
 
2279
2357
  // src/page-data.ts
2280
- function isPlainObject3(value) {
2358
+ function isPlainObject4(value) {
2281
2359
  return typeof value === "object" && value !== null && !Array.isArray(value);
2282
2360
  }
2283
2361
  function planPageData(data, resolve) {
2284
2362
  if (data === void 0) return [];
2285
- if (!isPlainObject3(data)) {
2363
+ if (!isPlainObject4(data)) {
2286
2364
  warnOnce("page-data:root", "[sdui] page.data\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4");
2287
2365
  return [];
2288
2366
  }
@@ -2292,7 +2370,7 @@ function planPageData(data, resolve) {
2292
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`);
2293
2371
  continue;
2294
2372
  }
2295
- if (!isPlainObject3(raw)) {
2373
+ if (!isPlainObject4(raw)) {
2296
2374
  warnOnce(`page-data:${key}`, `[sdui] page.data.${key}\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 \u2014 \uAC74\uB108\uB701\uB2C8\uB2E4`);
2297
2375
  continue;
2298
2376
  }
@@ -2327,7 +2405,7 @@ function pickPath(source, path) {
2327
2405
  if (path === void 0 || path === "") return source;
2328
2406
  let value = source;
2329
2407
  for (const segment of path.split(".")) {
2330
- if (!isPlainObject3(value)) return void 0;
2408
+ if (!isPlainObject4(value)) return void 0;
2331
2409
  value = value[segment];
2332
2410
  }
2333
2411
  return value;
@@ -2358,26 +2436,26 @@ function nearestArgsFrame(scope) {
2358
2436
  }
2359
2437
  return void 0;
2360
2438
  }
2361
- function isPlainObject4(v) {
2439
+ function isPlainObject5(v) {
2362
2440
  return typeof v === "object" && v !== null && !Array.isArray(v);
2363
2441
  }
2364
2442
  function isStateRef(v) {
2365
- return isPlainObject4(v) && typeof v.$state === "string";
2443
+ return isPlainObject5(v) && typeof v.$state === "string";
2366
2444
  }
2367
2445
  function isItemRef(v) {
2368
- return isPlainObject4(v) && "$item" in v;
2446
+ return isPlainObject5(v) && "$item" in v;
2369
2447
  }
2370
2448
  function isIndexRef(v) {
2371
- return isPlainObject4(v) && "$index" in v;
2449
+ return isPlainObject5(v) && "$index" in v;
2372
2450
  }
2373
2451
  function isCalcRef(v) {
2374
- return isPlainObject4(v) && isPlainObject4(v.$calc);
2452
+ return isPlainObject5(v) && isPlainObject5(v.$calc);
2375
2453
  }
2376
2454
  function isArgRef(v) {
2377
- return isPlainObject4(v) && typeof v.$arg === "string";
2455
+ return isPlainObject5(v) && typeof v.$arg === "string";
2378
2456
  }
2379
2457
  function isAction(v) {
2380
- return isPlainObject4(v) && typeof v.action === "string";
2458
+ return isPlainObject5(v) && typeof v.action === "string";
2381
2459
  }
2382
2460
  function isActionList(v) {
2383
2461
  return Array.isArray(v) && v.length > 0 && v.every(isAction);
@@ -2401,7 +2479,7 @@ function expandActionEntries(raw, scope) {
2401
2479
  return [];
2402
2480
  }
2403
2481
  function eventValue(arg) {
2404
- if (isPlainObject4(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2482
+ if (isPlainObject5(arg) && "target" in arg && typeof arg.preventDefault === "function") {
2405
2483
  const target = arg.target;
2406
2484
  if (target.type === "checkbox") return target.checked;
2407
2485
  if (target.type === "number" || target.type === "range") {
@@ -2511,7 +2589,7 @@ function childrenOf(node) {
2511
2589
  }
2512
2590
  function renderChild(child, index, scope, engine) {
2513
2591
  if (typeof child === "string") return child;
2514
- if (!isPlainObject4(child)) {
2592
+ if (!isPlainObject5(child)) {
2515
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)}`);
2516
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);
2517
2595
  }
@@ -2526,11 +2604,11 @@ function SduiNodeRenderer({
2526
2604
  engine
2527
2605
  }) {
2528
2606
  const { registry, resolveValue, dispatch, resolveAsset } = engine;
2529
- const slots = isPlainObject4(node.slots) ? node.slots : void 0;
2607
+ const slots = isPlainObject5(node.slots) ? node.slots : void 0;
2530
2608
  const childList = childrenOf(node);
2531
2609
  if (node.component === "ForEach") {
2532
2610
  const source = resolveValue(
2533
- isPlainObject4(node.props) ? node.props.source ?? null : null,
2611
+ isPlainObject5(node.props) ? node.props.source ?? null : null,
2534
2612
  scope
2535
2613
  );
2536
2614
  const rows = Array.isArray(source) ? source : [];
@@ -2540,7 +2618,7 @@ function SduiNodeRenderer({
2540
2618
  }
2541
2619
  return /* @__PURE__ */ jsx(Fragment, { children: rows.map((item, rowIndex) => {
2542
2620
  const rowScope = [...scope, { item, index: rowIndex }];
2543
- const rowKey = isPlainObject4(item) && typeof item.id === "string" ? item.id : rowIndex;
2621
+ const rowKey = isPlainObject5(item) && typeof item.id === "string" ? item.id : rowIndex;
2544
2622
  return /* @__PURE__ */ jsx(React9.Fragment, { children: childList?.map((child, i) => renderChild(child, i, rowScope, engine)) }, rowKey);
2545
2623
  }) });
2546
2624
  }
@@ -2559,7 +2637,7 @@ function SduiNodeRenderer({
2559
2637
  ] });
2560
2638
  }
2561
2639
  const props = {};
2562
- 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 : {})) {
2563
2641
  if (DENIED_PROPS2.has(name)) {
2564
2642
  warnOnce2(`prop:${name}`, `[sdui] \uC2A4\uD399\uC5D0\uC11C \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD504\uB86D: ${name} \u2014 \uBB34\uC2DC\uD569\uB2C8\uB2E4`);
2565
2643
  continue;
@@ -2598,7 +2676,7 @@ function SduiNodeRenderer({
2598
2676
  if (childLayout) {
2599
2677
  for (const name of CHILD_LAYOUT_PROPS) delete props[name];
2600
2678
  if (childLayout.style) {
2601
- props.style = isPlainObject4(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2679
+ props.style = isPlainObject5(props.style) ? { ...childLayout.style, ...props.style } : childLayout.style;
2602
2680
  }
2603
2681
  if (childLayout.className) {
2604
2682
  props.className = typeof props.className === "string" && props.className ? `${childLayout.className} ${props.className}` : childLayout.className;
@@ -2622,7 +2700,7 @@ var FRAGMENT_MAX_DEPTH = 4;
2622
2700
  function SduiFragment({ node, scope, engine }) {
2623
2701
  const adapter = useSduiAdapter();
2624
2702
  const chain = React9.useContext(SduiFragmentChainContext);
2625
- const raw = isPlainObject4(node.props) ? node.props.id : void 0;
2703
+ const raw = isPlainObject5(node.props) ? node.props.id : void 0;
2626
2704
  const id = engine.resolveValue(raw, scope);
2627
2705
  if (typeof id !== "string" || id === "") {
2628
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");
@@ -2642,8 +2720,8 @@ function SduiFragment({ node, scope, engine }) {
2642
2720
  );
2643
2721
  return null;
2644
2722
  }
2645
- const rawArgs = isPlainObject4(node.props) ? node.props.args : void 0;
2646
- if (rawArgs !== void 0 && !isPlainObject4(rawArgs)) {
2723
+ const rawArgs = isPlainObject5(node.props) ? node.props.args : void 0;
2724
+ if (rawArgs !== void 0 && !isPlainObject5(rawArgs)) {
2647
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`);
2648
2726
  }
2649
2727
  const useSpec = adapter?.useSpec;
@@ -2658,7 +2736,7 @@ function SduiFragment({ node, scope, engine }) {
2658
2736
  SduiFragmentBody,
2659
2737
  {
2660
2738
  id,
2661
- args: isPlainObject4(rawArgs) ? rawArgs : void 0,
2739
+ args: isPlainObject5(rawArgs) ? rawArgs : void 0,
2662
2740
  useSpec,
2663
2741
  chain,
2664
2742
  scope,
@@ -2679,13 +2757,13 @@ function SduiFragmentBody({
2679
2757
  const nextChain = React9.useMemo(() => [...chain, id], [chain, id]);
2680
2758
  const bodyScope = React9.useMemo(() => [...scope, { args, callSiteScope: scope }], [scope, args]);
2681
2759
  React9.useEffect(() => {
2682
- if (!DEV || !isPlainObject4(spec) || !Array.isArray(spec.body)) return;
2760
+ if (!DEV || !isPlainObject5(spec) || !Array.isArray(spec.body)) return;
2683
2761
  validateSpec(spec, new Set(Object.keys(engine.registry))).forEach(
2684
2762
  (issue) => console.warn(`[sdui:spec:${id}] ${issue.path} \u2014 ${issue.message}`)
2685
2763
  );
2686
2764
  }, [spec, engine.registry, id]);
2687
2765
  if (spec === void 0) return null;
2688
- if (spec === null || !isPlainObject4(spec) || !Array.isArray(spec.body)) {
2766
+ if (spec === null || !isPlainObject5(spec) || !Array.isArray(spec.body)) {
2689
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`);
2690
2768
  return null;
2691
2769
  }
@@ -2815,7 +2893,7 @@ function SduiRenderer({
2815
2893
  if (head in current) {
2816
2894
  let value = current[head];
2817
2895
  for (const segment of segments.slice(cut)) {
2818
- if (!isPlainObject4(value)) return void 0;
2896
+ if (!isPlainObject5(value)) return void 0;
2819
2897
  value = value[segment];
2820
2898
  }
2821
2899
  return value;
@@ -2833,7 +2911,7 @@ function SduiRenderer({
2833
2911
  const frame = nearestItemFrame(scope);
2834
2912
  if (!frame) return null;
2835
2913
  if (value.$item === true) return frame.item;
2836
- return isPlainObject4(frame.item) ? frame.item[value.$item] ?? null : null;
2914
+ return isPlainObject5(frame.item) ? frame.item[value.$item] ?? null : null;
2837
2915
  }
2838
2916
  if (isIndexRef(value)) {
2839
2917
  return nearestItemFrame(scope)?.index ?? null;
@@ -2857,7 +2935,7 @@ function SduiRenderer({
2857
2935
  return evaluateCalc(calc, resolved, (late) => resolveValue(late, scope));
2858
2936
  }
2859
2937
  if (Array.isArray(value)) return value.map((v) => resolveValue(v, scope));
2860
- if (isPlainObject4(value)) {
2938
+ if (isPlainObject5(value)) {
2861
2939
  const out = {};
2862
2940
  for (const [k, v] of Object.entries(value)) {
2863
2941
  out[k] = deferChainEntry(value, k) ? v : resolveValue(v, scope);
@@ -2965,9 +3043,27 @@ function SduiRenderer({
2965
3043
  }
2966
3044
  hostRef.current.navigate(action.to);
2967
3045
  break;
2968
- case "setState":
2969
- 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 });
2970
3065
  break;
3066
+ }
2971
3067
  case "open":
2972
3068
  setEntries({ [action.key]: true });
2973
3069
  break;
@@ -3026,7 +3122,7 @@ function SduiRenderer({
3026
3122
  break;
3027
3123
  }
3028
3124
  const row = current[index];
3029
- if (!isPlainObject4(row)) break;
3125
+ if (!isPlainObject5(row)) break;
3030
3126
  const nextValue = (action.fromEvent ? eventValue(arg) : action.value) ?? null;
3031
3127
  const next = [...current];
3032
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.44.0",
3
+ "version": "0.45.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",