@webskill/sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import { a as __require, i as __reExport, n as __esmMin, o as __toCommonJS, r as __exportAll$2, s as __toESM$1, t as __commonJSMin$1 } from "./rolldown-runtime-BOF7iYI8.js";
2
2
  import { m as WebSkillError } from "./dist-8oQRa8Xz.js";
3
- import { G as mountEchart, U as chartSpecFromProps } from "./dist-D9Lcn5Pp.js";
3
+ import { M as qualifyFieldName, Q as chartSpecFromProps, et as mountEchart, v as collectFormScopes, y as collectScopedValues } from "./dist-bewtXYlO.js";
4
4
  import minpath$1 from "node:path";
5
5
  import { fileURLToPath as urlToPath$1 } from "node:url";
6
6
  import * as React$10 from "react";
7
- import React, { Fragment, createContext, createElement, forwardRef, memo, useCallback, useContext, useDeferredValue, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, default as react_default } from "react";
7
+ import React, { Fragment, createContext, createElement, forwardRef, memo, useCallback, useContext, useDeferredValue, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, default as react_default } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  import * as ReactDOM from "react-dom";
10
10
  import minproc$1 from "node:process";
@@ -5551,14 +5551,14 @@ var __name$10 = (target, value) => __defProp$12(target, "name", {
5551
5551
  });
5552
5552
  var useReactId = React$10[" useId ".trim().toString()] || (() => void 0);
5553
5553
  var count = 0;
5554
- function useId(deterministicId) {
5554
+ function useId$1(deterministicId) {
5555
5555
  const [id, setId] = React$10.useState(useReactId());
5556
5556
  useLayoutEffect2(() => {
5557
5557
  if (!deterministicId) setId((reactId) => reactId ?? String(count++));
5558
5558
  }, [deterministicId]);
5559
5559
  return deterministicId || (id ? `radix-${id}` : "");
5560
5560
  }
5561
- __name$10(useId, "useId");
5561
+ __name$10(useId$1, "useId");
5562
5562
 
5563
5563
  //#endregion
5564
5564
  //#region ../../node_modules/.pnpm/@floating-ui+utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs
@@ -7564,7 +7564,7 @@ var RovingFocusGroupImpl = /* @__PURE__ */ React$10.forwardRef(/* @__PURE__ */ _
7564
7564
  var ITEM_NAME$1 = "RovingFocusGroupItem";
7565
7565
  var RovingFocusGroupItem = /* @__PURE__ */ React$10.forwardRef(/* @__PURE__ */ __name$6(function RovingFocusGroupItem2(props, forwardedRef) {
7566
7566
  const { __scopeRovingFocusGroup, focusable = true, active = false, tabStopId, children, ...itemProps } = props;
7567
- const autoId = useId();
7567
+ const autoId = useId$1();
7568
7568
  const id = tabStopId || autoId;
7569
7569
  const context = useRovingFocusContext(ITEM_NAME$1, __scopeRovingFocusGroup);
7570
7570
  const isCurrentTabStop = context.currentTabStopId === id;
@@ -10241,9 +10241,9 @@ var DropdownMenu$1 = /* @__PURE__ */ __name$4((props) => {
10241
10241
  });
10242
10242
  return /* @__PURE__ */ jsx(DropdownMenuProvider, {
10243
10243
  scope: __scopeDropdownMenu,
10244
- triggerId: useId(),
10244
+ triggerId: useId$1(),
10245
10245
  triggerRef,
10246
- contentId: useId(),
10246
+ contentId: useId$1(),
10247
10247
  open,
10248
10248
  onOpenChange: setOpen,
10249
10249
  onOpenToggle: React$10.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen]),
@@ -11157,9 +11157,9 @@ var Dialog = /* @__PURE__ */ __name((props) => {
11157
11157
  scope: __scopeDialog,
11158
11158
  triggerRef,
11159
11159
  contentRef,
11160
- contentId: useId(),
11161
- titleId: useId(),
11162
- descriptionId: useId(),
11160
+ contentId: useId$1(),
11161
+ titleId: useId$1(),
11162
+ descriptionId: useId$1(),
11163
11163
  titlePresent: titleCount > 0,
11164
11164
  descriptionPresent: descriptionCount > 0,
11165
11165
  setTitleCount,
@@ -11349,7 +11349,7 @@ var me = React$10.forwardRef((r, o) => {
11349
11349
  groups: /* @__PURE__ */ new Set()
11350
11350
  }
11351
11351
  };
11352
- }), u = L(() => /* @__PURE__ */ new Set()), c = L(() => /* @__PURE__ */ new Map()), d = L(() => /* @__PURE__ */ new Map()), f = L(() => /* @__PURE__ */ new Set()), p = pe(r), { label: b, children: m, value: R, onValueChange: x, filter: C, shouldFilter: S, loop: A, disablePointerSelection: ge = !1, vimBindings: j = !0, ...O } = r, $ = useId(), q = useId(), _ = useId(), I = React$10.useRef(null), v = ke();
11352
+ }), u = L(() => /* @__PURE__ */ new Set()), c = L(() => /* @__PURE__ */ new Map()), d = L(() => /* @__PURE__ */ new Map()), f = L(() => /* @__PURE__ */ new Set()), p = pe(r), { label: b, children: m, value: R, onValueChange: x, filter: C, shouldFilter: S, loop: A, disablePointerSelection: ge = !1, vimBindings: j = !0, ...O } = r, $ = useId$1(), q = useId$1(), _ = useId$1(), I = React$10.useRef(null), v = ke();
11353
11353
  k(() => {
11354
11354
  if (R !== void 0) {
11355
11355
  let e = R.trim();
@@ -11547,7 +11547,7 @@ var me = React$10.forwardRef((r, o) => {
11547
11547
  });
11548
11548
  var he = React$10.forwardRef((r, o) => {
11549
11549
  var _, I;
11550
- let n = useId(), u = React$10.useRef(null), c = React$10.useContext(fe), d = K(), f = pe(r), p = (I = (_ = f.current) == null ? void 0 : _.forceMount) != null ? I : c == null ? void 0 : c.forceMount;
11550
+ let n = useId$1(), u = React$10.useRef(null), c = React$10.useContext(fe), d = K(), f = pe(r), p = (I = (_ = f.current) == null ? void 0 : _.forceMount) != null ? I : c == null ? void 0 : c.forceMount;
11551
11551
  k(() => {
11552
11552
  if (!p) return d.item(n, c == null ? void 0 : c.id);
11553
11553
  }, [p]);
@@ -11588,7 +11588,7 @@ var he = React$10.forwardRef((r, o) => {
11588
11588
  }, r.children);
11589
11589
  });
11590
11590
  var Ee = React$10.forwardRef((r, o) => {
11591
- let { heading: n, children: u, forceMount: c, ...d } = r, f = useId(), p = React$10.useRef(null), b = React$10.useRef(null), m = useId(), R = K(), x = P((S) => c || R.filter() === !1 ? !0 : S.search ? S.filtered.groups.has(f) : !0);
11591
+ let { heading: n, children: u, forceMount: c, ...d } = r, f = useId$1(), p = React$10.useRef(null), b = React$10.useRef(null), m = useId$1(), R = K(), x = P((S) => c || R.filter() === !1 ? !0 : S.search ? S.filtered.groups.has(f) : !0);
11592
11592
  k(() => R.group(f), []), ve(f, p, [
11593
11593
  r.value,
11594
11594
  r.heading,
@@ -14886,7 +14886,7 @@ function checkRule(state) {
14886
14886
  * @param {Info} info
14887
14887
  * @returns {string}
14888
14888
  */
14889
- function list$4(node, parent, state, info) {
14889
+ function list$5(node, parent, state, info) {
14890
14890
  const exit = state.enter("list");
14891
14891
  const bulletCurrent = state.bulletCurrent;
14892
14892
  /** @type {string} */
@@ -15172,7 +15172,7 @@ const handle = {
15172
15172
  inlineCode: inlineCode$2,
15173
15173
  link: link$2,
15174
15174
  linkReference: linkReference$2,
15175
- list: list$4,
15175
+ list: list$5,
15176
15176
  listItem: listItem$2,
15177
15177
  paragraph: paragraph$2,
15178
15178
  root: root$4,
@@ -24087,7 +24087,7 @@ function tokenizeThematicBreak$1(effects, ok, nok) {
24087
24087
  * } from 'micromark-util-types'
24088
24088
  */
24089
24089
  /** @type {Construct} */
24090
- const list$3 = {
24090
+ const list$4 = {
24091
24091
  continuation: { tokenize: tokenizeListContinuation$1 },
24092
24092
  exit: tokenizeListEnd$1,
24093
24093
  name: "list",
@@ -24207,7 +24207,7 @@ function tokenizeListContinuation$1(effects, ok, nok) {
24207
24207
  function notInCurrentItem(code) {
24208
24208
  self.containerState._closeFlow = true;
24209
24209
  self.interrupt = void 0;
24210
- return factorySpace$1(effects, effects.attempt(list$3, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
24210
+ return factorySpace$1(effects, effects.attempt(list$4, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
24211
24211
  }
24212
24212
  }
24213
24213
  /**
@@ -34368,19 +34368,19 @@ var constructs_exports$1 = /* @__PURE__ */ __exportAll$2({
34368
34368
  });
34369
34369
  /** @satisfies {Extension['document']} */
34370
34370
  const document$3 = {
34371
- [42]: list$3,
34372
- [43]: list$3,
34373
- [45]: list$3,
34374
- [48]: list$3,
34375
- [49]: list$3,
34376
- [50]: list$3,
34377
- [51]: list$3,
34378
- [52]: list$3,
34379
- [53]: list$3,
34380
- [54]: list$3,
34381
- [55]: list$3,
34382
- [56]: list$3,
34383
- [57]: list$3,
34371
+ [42]: list$4,
34372
+ [43]: list$4,
34373
+ [45]: list$4,
34374
+ [48]: list$4,
34375
+ [49]: list$4,
34376
+ [50]: list$4,
34377
+ [51]: list$4,
34378
+ [52]: list$4,
34379
+ [53]: list$4,
34380
+ [54]: list$4,
34381
+ [55]: list$4,
34382
+ [56]: list$4,
34383
+ [57]: list$4,
34384
34384
  [62]: blockQuote$1
34385
34385
  };
34386
34386
  /** @satisfies {Extension['contentInitial']} */
@@ -36686,7 +36686,7 @@ function listItemLoose$1(node) {
36686
36686
  * @returns {Element}
36687
36687
  * hast node.
36688
36688
  */
36689
- function list$2(state, node) {
36689
+ function list$3(state, node) {
36690
36690
  /** @type {Properties} */
36691
36691
  const properties = {};
36692
36692
  const results = state.all(node);
@@ -37081,7 +37081,7 @@ const handlers$1 = {
37081
37081
  linkReference: linkReference$1,
37082
37082
  link: link$1,
37083
37083
  listItem: listItem$1,
37084
- list: list$2,
37084
+ list: list$3,
37085
37085
  paragraph: paragraph$1,
37086
37086
  root: root$2,
37087
37087
  strong: strong$1,
@@ -99762,7 +99762,6 @@ function DataTable({ children, className = "", containerClassName = "", ...props
99762
99762
  }
99763
99763
  const toneClasses$1 = {
99764
99764
  neutral: "bg-subtle text-muted",
99765
- primary: "bg-subtle text-ink",
99766
99765
  success: "bg-success-soft text-success",
99767
99766
  warning: "bg-warning-soft text-warning",
99768
99767
  info: "bg-info-soft text-info",
@@ -111452,7 +111451,7 @@ function listItemLoose(node) {
111452
111451
  * @returns {Element}
111453
111452
  * hast node.
111454
111453
  */
111455
- function list(state, node) {
111454
+ function list$2(state, node) {
111456
111455
  /** @type {Properties} */
111457
111456
  const properties = {};
111458
111457
  const results = state.all(node);
@@ -111817,7 +111816,7 @@ const handlers = {
111817
111816
  linkReference,
111818
111817
  link,
111819
111818
  listItem,
111820
- list,
111819
+ list: list$2,
111821
111820
  paragraph,
111822
111821
  root,
111823
111822
  strong,
@@ -120087,7 +120086,7 @@ const MarkdownText = memo(function MarkdownText({ className }) {
120087
120086
  });
120088
120087
 
120089
120088
  //#endregion
120090
- //#region ../ui-react/dist/catalogComponents-KsujmL4b.js
120089
+ //#region ../ui-react/dist/catalogComponents-DV7cPpUm.js
120091
120090
  /** echarts 挂载点:surface 图表与 catalog 声明树的 Chart 节点共用,实现在 `@webskill/ui`,与 A2UI 档同一份 */
120092
120091
  function EChart({ chart }) {
120093
120092
  const containerRef = useRef(null);
@@ -120125,19 +120124,53 @@ function useSurfaceForm({ surfaceId, spec, actions, runId, draft, onAction, onDr
120125
120124
  collectFields(spec, out);
120126
120125
  return out;
120127
120126
  }, [spec]);
120127
+ const scopes = useMemo(() => collectFormScopes(spec), [spec]);
120128
+ const scoping = scopes.length > 1;
120129
+ const scopeFields = useMemo(() => new Map(scopes.map((scope) => {
120130
+ const out = [];
120131
+ collectFields(scope.form, out);
120132
+ return [scope.scopeId, out];
120133
+ })), [scopes]);
120134
+ const nodeScopes = useMemo(() => new Map(scopes.map((scope) => [scope.form, scope])), [scopes]);
120135
+ const defaults = useMemo(() => {
120136
+ const out = {};
120137
+ const add = (field, scopeId) => {
120138
+ if (field["defaultValue"] !== void 0) out[qualifyFieldName(String(field["name"]), scopeId)] = field["defaultValue"];
120139
+ };
120140
+ if (!scoping) {
120141
+ for (const field of fields) add(field);
120142
+ return out;
120143
+ }
120144
+ for (const scope of scopes) for (const field of scopeFields.get(scope.scopeId) ?? []) add(field, scope.scopeId);
120145
+ return out;
120146
+ }, [
120147
+ fields,
120148
+ scopes,
120149
+ scopeFields,
120150
+ scoping
120151
+ ]);
120152
+ const claims = useRef({
120153
+ spec,
120154
+ map: /* @__PURE__ */ new Map()
120155
+ });
120156
+ if (claims.current.spec !== spec) claims.current = {
120157
+ spec,
120158
+ map: /* @__PURE__ */ new Map()
120159
+ };
120128
120160
  const values = {
120129
- ...useMemo(() => Object.fromEntries(fields.filter((field) => field["defaultValue"] !== void 0).map((field) => [String(field["name"]), field["defaultValue"]])), [fields]),
120161
+ ...defaults,
120130
120162
  ...touched
120131
120163
  };
120132
120164
  const resolveActionId = (intent, nodeId) => nodeId ?? actions.find((action) => action.intent === intent)?.id ?? intent;
120133
- const dispatch = (actionId, withValues) => {
120165
+ const dispatch = (actionId, withValues, scope) => {
120134
120166
  const declared = actions.find((action) => action.id === actionId);
120135
120167
  onAction?.({
120136
120168
  surfaceId,
120137
120169
  actionId,
120138
120170
  intent: declared?.intent ?? "select",
120139
120171
  ...declared?.nonce ? { nonce: declared.nonce } : {},
120140
- ...withValues ? { value: { ...values } } : {}
120172
+ ...scope ? { scopeId: scope.scopeId } : {},
120173
+ ...withValues ? { value: collectScopedValues(values, scope) } : {}
120141
120174
  });
120142
120175
  };
120143
120176
  return {
@@ -120145,45 +120178,63 @@ function useSurfaceForm({ surfaceId, spec, actions, runId, draft, onAction, onDr
120145
120178
  actions,
120146
120179
  values,
120147
120180
  errors,
120181
+ scopes,
120148
120182
  resolveActionId,
120149
- setValue: (name, value) => {
120183
+ scopeOfNode: (node) => scoping ? nodeScopes.get(node) : void 0,
120184
+ claimScope: (instanceId, formProps) => {
120185
+ if (!scoping) return void 0;
120186
+ const claimed = claims.current.map.get(instanceId);
120187
+ if (claimed) return claimed;
120188
+ const taken = new Set([...claims.current.map.values()].map((scope) => scope.scopeId));
120189
+ const match = scopes.find((scope) => !taken.has(scope.scopeId) && Object.entries(scope.form.props ?? {}).every(([key, value]) => value === formProps[key]));
120190
+ if (match) claims.current.map.set(instanceId, match);
120191
+ return match;
120192
+ },
120193
+ setValue: (key, value) => {
120150
120194
  const next = {
120151
120195
  ...touched,
120152
- [name]: value
120196
+ [key]: value
120153
120197
  };
120154
120198
  setTouched(next);
120155
- setErrors(({ [name]: _cleared, ...rest }) => rest);
120199
+ setErrors(({ [key]: _cleared, ...rest }) => rest);
120156
120200
  if (runId) onDraftChange?.({
120157
120201
  runId,
120158
120202
  surfaceId,
120159
120203
  value: next
120160
120204
  });
120161
120205
  },
120162
- emit: (actionId) => dispatch(actionId, false),
120163
- submit: (intent, nodeId) => {
120164
- const missing = fields.filter((field) => field["required"] === true && isBlank(values[String(field["name"])]));
120206
+ emit: (actionId, scope) => dispatch(actionId, false, scope),
120207
+ submit: (intent, nodeId, scope) => {
120208
+ const missing = (scope ? scopeFields.get(scope.scopeId) ?? [] : fields).filter((field) => field["required"] === true && isBlank(values[qualifyFieldName(String(field["name"]), scope?.scopeId)]));
120165
120209
  if (missing.length > 0) {
120166
- setErrors(Object.fromEntries(missing.map((field) => [String(field["name"]), REQUIRED_MESSAGE])));
120210
+ const flagged = Object.fromEntries(missing.map((field) => [qualifyFieldName(String(field["name"]), scope?.scopeId), REQUIRED_MESSAGE]));
120211
+ setErrors((prev) => ({
120212
+ ...scope ? withoutScope(prev, scope.scopeId) : {},
120213
+ ...flagged
120214
+ }));
120167
120215
  return;
120168
120216
  }
120169
- setErrors({});
120170
- dispatch(resolveActionId(intent, nodeId), true);
120217
+ setErrors((prev) => scope ? withoutScope(prev, scope.scopeId) : {});
120218
+ dispatch(scope && !nodeId ? scope.submitActionId : resolveActionId(intent, nodeId), true, scope);
120171
120219
  }
120172
120220
  };
120173
120221
  }
120222
+ const withoutScope = (errors, scopeId) => Object.fromEntries(Object.entries(errors).filter(([key]) => !key.startsWith(`${scopeId}.`)));
120174
120223
  /** catalog `Field` 的唯一控件实现:8 种 type 的取值语义在这里定死 */
120175
- function SurfaceField({ form, props }) {
120224
+ function SurfaceField({ form, props, scope }) {
120176
120225
  const name = str(props, "name");
120226
+ const key = qualifyFieldName(name, scope?.scopeId);
120177
120227
  const label = str(props, "label", name);
120178
120228
  const type = str(props, "type", "text");
120179
120229
  const required = props["required"] === true;
120180
- const value = form.values[name];
120181
- const error = form.errors[name];
120182
- const errorId = error ? `webskill-surface-${form.surfaceId}-${name}-error` : void 0;
120230
+ const value = form.values[key];
120231
+ const error = form.errors[key];
120232
+ const errorId = error ? `webskill-surface-${form.surfaceId}-${key}-error` : void 0;
120183
120233
  const options = Array.isArray(props["options"]) ? props["options"] : [];
120234
+ const suggestion = props["suggestion"];
120184
120235
  /** 控件的机器可读锚点:宿主与测试据此定位字段,不依赖标签文案 */
120185
120236
  const anchor = {
120186
- "data-webskill-surface-control": name,
120237
+ "data-webskill-surface-control": key,
120187
120238
  ...error ? {
120188
120239
  "aria-invalid": true,
120189
120240
  "aria-describedby": errorId
@@ -120191,17 +120242,17 @@ function SurfaceField({ form, props }) {
120191
120242
  };
120192
120243
  const control = () => {
120193
120244
  if (type === "textarea") return /* @__PURE__ */ jsx(Textarea, {
120194
- id: name,
120245
+ id: key,
120195
120246
  ...anchor,
120196
120247
  invalid: Boolean(error),
120197
120248
  value: typeof value === "string" ? value : "",
120198
- onChange: (event) => form.setValue(name, event.target.value)
120249
+ onChange: (event) => form.setValue(key, event.target.value)
120199
120250
  });
120200
120251
  if (type === "toggle") return /* @__PURE__ */ jsx(Switch, {
120201
- id: name,
120252
+ id: key,
120202
120253
  ...anchor,
120203
120254
  checked: value === true,
120204
- onCheckedChange: (next) => form.setValue(name, next)
120255
+ onCheckedChange: (next) => form.setValue(key, next)
120205
120256
  });
120206
120257
  if (type === "select" || type === "multi-select") {
120207
120258
  const multiple = type === "multi-select";
@@ -120210,13 +120261,13 @@ function SurfaceField({ form, props }) {
120210
120261
  return index < 0 ? "" : String(index);
120211
120262
  };
120212
120263
  return /* @__PURE__ */ jsx("select", {
120213
- id: name,
120264
+ id: key,
120214
120265
  ...anchor,
120215
120266
  multiple,
120216
120267
  value: multiple ? Array.isArray(value) ? value.map(indexOf) : [] : indexOf(value),
120217
120268
  onChange: (event) => {
120218
120269
  const pick = (index) => options[Number(index)].value;
120219
- form.setValue(name, multiple ? [...event.target.selectedOptions].map((option) => pick(option.value)) : pick(event.target.value));
120270
+ form.setValue(key, multiple ? [...event.target.selectedOptions].map((option) => pick(option.value)) : pick(event.target.value));
120220
120271
  },
120221
120272
  children: options.map((option, index) => /* @__PURE__ */ jsx("option", {
120222
120273
  value: String(index),
@@ -120225,29 +120276,40 @@ function SurfaceField({ form, props }) {
120225
120276
  });
120226
120277
  }
120227
120278
  if (type === "file") return /* @__PURE__ */ jsx("input", {
120228
- id: name,
120279
+ id: key,
120229
120280
  ...anchor,
120230
120281
  type: "file",
120231
- onChange: (event) => form.setValue(name, event.target.files?.[0]?.name ?? "")
120282
+ onChange: (event) => form.setValue(key, event.target.files?.[0]?.name ?? "")
120232
120283
  });
120233
120284
  return /* @__PURE__ */ jsx(Input, {
120234
- id: name,
120285
+ id: key,
120235
120286
  ...anchor,
120236
120287
  invalid: Boolean(error),
120237
120288
  type: type === "number" ? "number" : type === "date" ? "date" : "text",
120238
120289
  value: typeof value === "string" || typeof value === "number" ? String(value) : "",
120239
- onChange: (event) => form.setValue(name, type === "number" ? Number(event.target.value) : event.target.value)
120290
+ onChange: (event) => form.setValue(key, type === "number" ? Number(event.target.value) : event.target.value)
120240
120291
  });
120241
120292
  };
120242
120293
  return /* @__PURE__ */ jsxs("label", {
120243
120294
  className: "flex flex-col gap-1 text-sm",
120244
- htmlFor: name,
120295
+ htmlFor: key,
120245
120296
  children: [
120246
120297
  /* @__PURE__ */ jsxs("span", {
120247
120298
  className: "text-ink",
120248
120299
  children: [label, required ? " *" : ""]
120249
120300
  }),
120250
120301
  control(),
120302
+ suggestion !== void 0 && isBlank(value) ? /* @__PURE__ */ jsxs("span", {
120303
+ "data-webskill-suggestion": key,
120304
+ className: "flex items-center gap-2 text-xs text-muted",
120305
+ children: [/* @__PURE__ */ jsxs("span", { children: ["Last entered: ", String(suggestion.value)] }), /* @__PURE__ */ jsx("button", {
120306
+ type: "button",
120307
+ "data-webskill-suggestion-use": key,
120308
+ onClick: () => form.setValue(key, suggestion.value),
120309
+ className: "rounded-md border border-border px-2 py-0.5 text-ink transition-colors hover:bg-accent",
120310
+ children: "Use"
120311
+ })]
120312
+ }) : null,
120251
120313
  props["description"] ? /* @__PURE__ */ jsx("span", {
120252
120314
  className: "text-xs text-muted",
120253
120315
  children: str(props, "description")
@@ -120262,26 +120324,28 @@ function SurfaceField({ form, props }) {
120262
120324
  });
120263
120325
  }
120264
120326
  /** catalog `Form` 的提交 / 取消按钮:提交带表单值,取消不带 */
120265
- function SurfaceFormButtons({ form, props }) {
120327
+ function SurfaceFormButtons({ form, props, scope }) {
120328
+ const submitId = scope ? scope.submitActionId : form.resolveActionId("submit");
120329
+ const cancelId = scope ? scope.cancelActionId : form.resolveActionId("cancel");
120266
120330
  return /* @__PURE__ */ jsxs("div", {
120267
120331
  className: "flex gap-2",
120268
120332
  children: [/* @__PURE__ */ jsx(Button, {
120269
120333
  type: "submit",
120270
120334
  size: "sm",
120271
- "data-webskill-surface-action": form.resolveActionId("submit"),
120335
+ "data-webskill-surface-action": submitId,
120272
120336
  children: str(props, "submitLabel", "Submit")
120273
120337
  }), props["cancelLabel"] ? /* @__PURE__ */ jsx(Button, {
120274
120338
  type: "button",
120275
120339
  size: "sm",
120276
120340
  variant: "secondary",
120277
- "data-webskill-surface-action": form.resolveActionId("cancel"),
120278
- onClick: () => form.emit(form.resolveActionId("cancel")),
120341
+ "data-webskill-surface-action": cancelId,
120342
+ onClick: () => form.emit(cancelId, scope),
120279
120343
  children: str(props, "cancelLabel")
120280
120344
  }) : null]
120281
120345
  });
120282
120346
  }
120283
120347
  /** catalog `Button` 的唯一实现:submit 走带值提交,其余走裸动作 */
120284
- function SurfaceButton({ form, props, nodeId }) {
120348
+ function SurfaceButton({ form, props, nodeId, scope }) {
120285
120349
  const intent = str(props, "action");
120286
120350
  const actionId = form.resolveActionId(intent, nodeId);
120287
120351
  return /* @__PURE__ */ jsx(Button, {
@@ -120289,11 +120353,92 @@ function SurfaceButton({ form, props, nodeId }) {
120289
120353
  type: "button",
120290
120354
  disabled: props["disabled"] === true,
120291
120355
  "data-webskill-surface-action": actionId,
120292
- onClick: () => intent === "submit" ? form.submit(intent, nodeId) : form.emit(actionId),
120356
+ onClick: () => intent === "submit" ? form.submit(intent, nodeId, scope) : form.emit(actionId, scope),
120293
120357
  children: str(props, "label")
120294
120358
  });
120295
120359
  }
120296
120360
  /**
120361
+ * FR-6.2 的四个布局/展示组件。四档渲染共用同一份实现:
120362
+ * native 的 switch 与 json-render / OpenUI 的组件表都调这里,
120363
+ * 语义(tablist、grid、ol/li、progressbar)因此不会随渲染器分叉。
120364
+ */
120365
+ const num = (props, key, fallback) => typeof props[key] === "number" ? props[key] : fallback;
120366
+ const list = (value) => Array.isArray(value) ? value : [];
120367
+ function SpecTabs({ props, children }) {
120368
+ const labels = list(props["labels"]);
120369
+ const panels = Array.isArray(children) ? children : [children];
120370
+ const [active, setActive] = useState(0);
120371
+ const index = Math.min(active, Math.max(panels.length - 1, 0));
120372
+ return /* @__PURE__ */ jsxs("div", {
120373
+ className: "flex flex-col gap-3",
120374
+ children: [/* @__PURE__ */ jsx("div", {
120375
+ role: "tablist",
120376
+ className: "flex gap-1 border-b border-border",
120377
+ children: labels.map((label, position) => /* @__PURE__ */ jsx(Button, {
120378
+ role: "tab",
120379
+ type: "button",
120380
+ size: "sm",
120381
+ variant: position === index ? "secondary" : "ghost",
120382
+ "aria-selected": position === index,
120383
+ onClick: () => setActive(position),
120384
+ children: label
120385
+ }, position))
120386
+ }), panels[index] ?? null]
120387
+ });
120388
+ }
120389
+ function SpecGrid({ props, children }) {
120390
+ return /* @__PURE__ */ jsx("div", {
120391
+ className: "grid gap-3",
120392
+ style: { gridTemplateColumns: `repeat(${Math.min(4, Math.max(2, num(props, "columns", 2)))}, minmax(0, 1fr))` },
120393
+ children
120394
+ });
120395
+ }
120396
+ function SpecTimeline({ props }) {
120397
+ return /* @__PURE__ */ jsx("ol", {
120398
+ className: "flex flex-col gap-2 pl-4",
120399
+ children: list(props["items"]).map((item, index) => /* @__PURE__ */ jsxs("li", {
120400
+ className: "flex flex-col gap-0.5",
120401
+ "data-state": str(item, "state", "pending"),
120402
+ children: [
120403
+ /* @__PURE__ */ jsx("strong", {
120404
+ className: "text-sm text-ink",
120405
+ children: str(item, "title")
120406
+ }),
120407
+ item["time"] ? /* @__PURE__ */ jsx("p", {
120408
+ className: "text-xs text-muted",
120409
+ children: str(item, "time")
120410
+ }) : null,
120411
+ item["description"] ? /* @__PURE__ */ jsx("p", {
120412
+ className: "text-sm text-ink",
120413
+ children: str(item, "description")
120414
+ }) : null
120415
+ ]
120416
+ }, index))
120417
+ });
120418
+ }
120419
+ function SpecProgress({ props }) {
120420
+ const value = Math.max(0, Math.min(100, num(props, "value", 0)));
120421
+ const label = str(props, "label");
120422
+ return /* @__PURE__ */ jsxs("div", {
120423
+ className: "flex flex-col gap-1",
120424
+ children: [label ? /* @__PURE__ */ jsx("span", {
120425
+ className: "text-xs text-muted",
120426
+ children: label
120427
+ }) : null, /* @__PURE__ */ jsx("div", {
120428
+ role: "progressbar",
120429
+ "aria-label": label || "Progress",
120430
+ "aria-valuenow": value,
120431
+ "aria-valuemin": 0,
120432
+ "aria-valuemax": 100,
120433
+ className: "h-2 overflow-hidden rounded-full bg-secondary",
120434
+ children: /* @__PURE__ */ jsx("span", {
120435
+ className: "block h-full bg-ink",
120436
+ style: { width: `${value}%` }
120437
+ })
120438
+ })]
120439
+ });
120440
+ }
120441
+ /**
120297
120442
  * json-render 与 OpenUI 两档的 Renderer 都只把 props 交给组件实现,拿不到节点 id,
120298
120443
  * 也拿不到表单状态,因此表单值与动作回传统一走这个 Context。
120299
120444
  * 缺省值故意不给空实现:没包 Provider 就是宿主接线错了,必须失败而不是“点了没反应”。
@@ -120317,6 +120462,8 @@ function CatalogSurfaceProvider({ surfaceId, spec, actions, onAction, children }
120317
120462
  children
120318
120463
  });
120319
120464
  }
120465
+ /** 当前子树所属的表单作用域;surface 上只有一个表单时为 undefined(行为与放宽前一致) */
120466
+ const FormScopeContext = createContext(void 0);
120320
120467
  /** 把实现表的条目包成真正的 React 组件,使其内部的 hook 调用处于自己的渲染周期 */
120321
120468
  function CatalogNode({ name, props, children }) {
120322
120469
  const Impl = catalogComponentImpls[name];
@@ -120328,40 +120475,47 @@ function CatalogNode({ name, props, children }) {
120328
120475
  }
120329
120476
  function CatalogForm({ props, children }) {
120330
120477
  const form = useCatalogSurfaceForm();
120331
- return /* @__PURE__ */ jsxs("form", {
120332
- className: "flex flex-col gap-3",
120333
- noValidate: true,
120334
- onSubmit: (event) => {
120335
- event.preventDefault();
120336
- form.submit("submit");
120337
- },
120338
- children: [
120339
- props["title"] ? /* @__PURE__ */ jsx("h3", {
120340
- className: "text-sm font-semibold text-ink",
120341
- children: str(props, "title")
120342
- }) : null,
120343
- children,
120344
- /* @__PURE__ */ jsx(SurfaceFormButtons, {
120345
- form,
120346
- props
120347
- })
120348
- ]
120478
+ const scope = form.claimScope(useId(), props);
120479
+ return /* @__PURE__ */ jsx(FormScopeContext.Provider, {
120480
+ value: scope,
120481
+ children: /* @__PURE__ */ jsxs("form", {
120482
+ className: "flex flex-col gap-3",
120483
+ noValidate: true,
120484
+ onSubmit: (event) => {
120485
+ event.preventDefault();
120486
+ form.submit("submit", void 0, scope);
120487
+ },
120488
+ children: [
120489
+ props["title"] ? /* @__PURE__ */ jsx("h3", {
120490
+ className: "text-sm font-semibold text-ink",
120491
+ children: str(props, "title")
120492
+ }) : null,
120493
+ children,
120494
+ /* @__PURE__ */ jsx(SurfaceFormButtons, {
120495
+ form,
120496
+ props,
120497
+ scope
120498
+ })
120499
+ ]
120500
+ })
120349
120501
  });
120350
120502
  }
120351
120503
  function CatalogField({ props }) {
120352
120504
  return /* @__PURE__ */ jsx(SurfaceField, {
120353
120505
  form: useCatalogSurfaceForm(),
120354
- props
120506
+ props,
120507
+ scope: useContext(FormScopeContext)
120355
120508
  });
120356
120509
  }
120357
120510
  function CatalogButton({ props }) {
120358
120511
  return /* @__PURE__ */ jsx(SurfaceButton, {
120359
120512
  form: useCatalogSurfaceForm(),
120360
- props
120513
+ props,
120514
+ scope: useContext(FormScopeContext)
120361
120515
  });
120362
120516
  }
120363
120517
  /**
120364
- * catalog 16 个组件的展示实现:json-render 与 OpenUI 两档共用同一份,
120518
+ * catalog 展示类组件的实现:json-render 与 OpenUI 两档共用同一份,
120365
120519
  * 避免"一个 catalog、多套 registry"退化成多套各自演进的视觉。
120366
120520
  * 交互(表单值收集 / action 回传)由各档的宿主组件负责。
120367
120521
  */
@@ -120433,6 +120587,16 @@ const catalogComponentImpls = {
120433
120587
  });
120434
120588
  },
120435
120589
  Chart: ({ props }) => /* @__PURE__ */ jsx(EChart, { chart: chartSpecFromProps(props) }),
120590
+ Tabs: ({ props, children }) => /* @__PURE__ */ jsx(SpecTabs, {
120591
+ props,
120592
+ children
120593
+ }),
120594
+ Grid: ({ props, children }) => /* @__PURE__ */ jsx(SpecGrid, {
120595
+ props,
120596
+ children
120597
+ }),
120598
+ Timeline: ({ props }) => /* @__PURE__ */ jsx(SpecTimeline, { props }),
120599
+ Progress: ({ props }) => /* @__PURE__ */ jsx(SpecProgress, { props }),
120436
120600
  FileLink: ({ props }) => /* @__PURE__ */ jsx("span", {
120437
120601
  className: "text-sm text-ink",
120438
120602
  children: str(props, "label", str(props, "path"))
@@ -120456,4 +120620,4 @@ const catalogComponentImpls = {
120456
120620
  };
120457
120621
 
120458
120622
  //#endregion
120459
- export { DropdownMenuContent as _, SurfaceField as a, Markdown as b, str as c, Badge as d, Button as f, DropdownMenuCheckboxItem as g, DropdownMenu as h, SurfaceButton as i, useCatalogSurfaceForm as l, DataTable as m, CatalogSurfaceProvider as n, SurfaceFormButtons as o, Checkbox as p, EChart as r, catalogComponentImpls as s, CatalogNode as t, useSurfaceForm as u, DropdownMenuTrigger as v, Separator as x, Input as y };
120623
+ export { Input as C, DropdownMenuTrigger as S, Separator as T, Checkbox as _, SpecProgress as a, DropdownMenuCheckboxItem as b, SurfaceButton as c, catalogComponentImpls as d, str as f, Button as g, Badge as h, SpecGrid as i, SurfaceField as l, useSurfaceForm as m, CatalogSurfaceProvider as n, SpecTabs as o, useCatalogSurfaceForm as p, EChart as r, SpecTimeline as s, CatalogNode as t, SurfaceFormButtons as u, DataTable as v, Markdown as w, DropdownMenuContent as x, DropdownMenu as y };