@fuaran-ui/ops 0.1.0 → 0.3.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Canonical-JSON codec (encoder + structural decoder) and tree-op apply engine for
4
4
  the Fuaran UI wire format — a conformant **TypeScript host** of the
5
5
  language-neutral contract specified in
6
- [`fuaran/docs/WIRE_FORMAT.md`](../../../fuaran/docs/WIRE_FORMAT.md), sibling to the
6
+ [`fuaran-dotnet/docs/WIRE_FORMAT.md`](../../../fuaran-dotnet/docs/WIRE_FORMAT.md), sibling to the
7
7
  F# host. Built on the typed shapes from
8
8
  [`@fuaran-ui/schema`](../schema/) (a peer dependency).
9
9
 
@@ -73,7 +73,7 @@ of the erased payloads is the host's responsibility.
73
73
  ## Conformance is the stability contract
74
74
 
75
75
  Per the wire-format **forward-coupling rule**
76
- ([`WIRE_FORMAT.md` §11](../../../fuaran/docs/WIRE_FORMAT.md)), adding a `NodeKind` / `Spec`
76
+ ([`WIRE_FORMAT.md` §11](../../../fuaran-dotnet/docs/WIRE_FORMAT.md)), adding a `NodeKind` / `Spec`
77
77
  / `TreeOp` / `Binding` / `Action` case updates the F# encoder + decoder + the corpus
78
78
  **and** this TS codec + `@fuaran-ui/schema` in the same commit. Byte-equality against the
79
79
  corpus — not just API non-breakage — is the contract.
package/dist/index.cjs CHANGED
@@ -362,8 +362,10 @@ var flushTrigger = (t) => {
362
362
  };
363
363
  var binding = (b, staticEnc = objValue) => {
364
364
  switch (b.kind) {
365
- case "Static":
366
- return caseObj("Static", [["value", staticEnc(b.value)]]);
365
+ case "Static": {
366
+ const valueFields = b.value === null || b.value === void 0 ? [] : [["value", staticEnc(b.value)]];
367
+ return caseObj("Static", valueFields);
368
+ }
367
369
  case "Query": {
368
370
  const dependsOnFields = b.dependsOn !== void 0 && b.dependsOn.length > 0 ? [["dependsOn", jArray(b.dependsOn.map((s) => str(s)))]] : [];
369
371
  return caseObj("Query", [...dependsOnFields, ["name", str(b.name)]]);
@@ -377,11 +379,10 @@ var binding = (b, staticEnc = objValue) => {
377
379
  const fieldField = b.field !== void 0 ? [["field", str(b.field)]] : [];
378
380
  return caseObj("Selection", [...defaultField, ...fieldField, ["nodeId", str(b.nodeId)]]);
379
381
  }
380
- case "State":
381
- return caseObj("State", [
382
- ["defaultValue", staticEnc(b.defaultValue)],
383
- ["key", str(b.key)]
384
- ]);
382
+ case "State": {
383
+ const defaultFields = b.defaultValue === null || b.defaultValue === void 0 ? [] : [["defaultValue", staticEnc(b.defaultValue)]];
384
+ return caseObj("State", [...defaultFields, ["key", str(b.key)]]);
385
+ }
385
386
  case "Computed":
386
387
  return caseObj("Computed", [["fn", CLOSURE]]);
387
388
  case "I18n": {
@@ -936,6 +937,25 @@ var formFieldKind = (autoBind, k) => {
936
937
  if (k.constraints.step !== void 0) fields.push(["step", num(k.constraints.step)]);
937
938
  return caseObj("Date", fields);
938
939
  }
940
+ case "DateRange": {
941
+ const staticPair = (p) => jObject([
942
+ ["from", str(p[0])],
943
+ ["to", str(p[1])]
944
+ ]);
945
+ const fields = [
946
+ ...handlerField("onChange", k.onChange),
947
+ ...valueField(
948
+ k.value,
949
+ schema.controlValueDefaults.dateRange,
950
+ (v) => v.kind === "Static" ? staticPair(v.value) : binding(v, staticPair)
951
+ ),
952
+ ["variant", str(k.variant)]
953
+ ];
954
+ if (k.constraints.min !== void 0) fields.push(["min", str(k.constraints.min)]);
955
+ if (k.constraints.max !== void 0) fields.push(["max", str(k.constraints.max)]);
956
+ if (k.constraints.step !== void 0) fields.push(["step", num(k.constraints.step)]);
957
+ return caseObj("DateRange", fields);
958
+ }
939
959
  default:
940
960
  return assertNever(k);
941
961
  }
@@ -1517,15 +1537,13 @@ var treeOp = (op) => {
1517
1537
  case "InsertChild":
1518
1538
  return caseObj("InsertChild", [
1519
1539
  ["child", node(op.child)],
1520
- ["parentId", str(op.parentId)],
1521
- ["position", num(op.position)]
1540
+ ["parentId", str(op.parentId)]
1522
1541
  ]);
1523
1542
  case "RemoveNode":
1524
1543
  return caseObj("RemoveNode", [["target", str(op.target)]]);
1525
1544
  case "MoveNode":
1526
1545
  return caseObj("MoveNode", [
1527
1546
  ["newParentId", str(op.newParentId)],
1528
- ["newPosition", num(op.newPosition)],
1529
1547
  ["target", str(op.target)]
1530
1548
  ]);
1531
1549
  case "ReorderChildren":
@@ -4034,9 +4052,8 @@ var decodeBinding = (path, j, parseStatic = (_p, v) => ok(decodeAstValue(v)), pl
4034
4052
  if (!d.ok) return d;
4035
4053
  switch (d.value) {
4036
4054
  case "Static": {
4037
- const v = requireField(path, f, "value", "Binding.Static value of the slot's expected type");
4038
- if (!v.ok) return v;
4039
- const parsed = parseStatic(`${path}.value`, v.value);
4055
+ const raw = f.get("value") ?? { kind: "JNull" };
4056
+ const parsed = parseStatic(`${path}.value`, raw);
4040
4057
  return parsed.ok ? ok({ kind: "Static", value: parsed.value }) : parsed;
4041
4058
  }
4042
4059
  case "Query": {
@@ -4113,7 +4130,7 @@ var decodeBinding = (path, j, parseStatic = (_p, v) => ok(decodeAstValue(v)), pl
4113
4130
  case "State": {
4114
4131
  const key = reqField(path, f, "key", "state key string", requireString);
4115
4132
  if (!key.ok) return key;
4116
- const dv = fieldAliased(f, "defaultValue", ["initialValue", "default"]);
4133
+ const dv = fieldAliased(f, "defaultValue", ["initialValue", "default"]) ?? { kind: "JNull" };
4117
4134
  let defaultValue = placeholder;
4118
4135
  if (dv !== void 0) {
4119
4136
  const parsed = parseStatic(`${path}.defaultValue`, dv);
@@ -4341,6 +4358,41 @@ var decodeBindingFloatPair = (p, j) => {
4341
4358
  }
4342
4359
  return decodeBinding(p, j, parseStaticFloatPair, [0, 0]);
4343
4360
  };
4361
+ var parseStaticStringPair = (p, v) => {
4362
+ const ordered = (a, b) => a > b ? makeError(
4363
+ "WRONG_TYPE",
4364
+ p,
4365
+ `date-range start '${a}' is after end '${b}' \u2014 a DateRange pair is ordered (from <= to); ISO-8601 strings of one variant compare lexicographically, so swap the two values`,
4366
+ 'ordered ISO-8601 pair ({"from": <iso>, "to": <iso>} with from <= to)'
4367
+ ) : ok([a, b]);
4368
+ if (v.kind === "JObject") {
4369
+ const from = v.fields.get("from");
4370
+ const to = v.fields.get("to");
4371
+ if (from !== void 0 && to !== void 0) {
4372
+ const a = requireString(`${p}.from`, from);
4373
+ if (!a.ok) return a;
4374
+ const b = requireString(`${p}.to`, to);
4375
+ if (!b.ok) return b;
4376
+ return ordered(a.value, b.value);
4377
+ }
4378
+ return wrongType(p, "object with from and to ISO-8601 strings");
4379
+ }
4380
+ if (v.kind === "JArray" && v.items.length === 2) {
4381
+ const a = requireString(`${p}[0]`, v.items[0]);
4382
+ if (!a.ok) return a;
4383
+ const b = requireString(`${p}[1]`, v.items[1]);
4384
+ if (!b.ok) return b;
4385
+ return ordered(a.value, b.value);
4386
+ }
4387
+ return wrongType(p, "date-range pair ({from, to} object or [from, to] array)");
4388
+ };
4389
+ var decodeBindingStringPair = (p, j) => {
4390
+ if (j.kind === "JObject" && j.fields.get("$type") === void 0 && j.fields.get("from") !== void 0 && j.fields.get("to") !== void 0) {
4391
+ const parsed = parseStaticStringPair(p, j);
4392
+ return parsed.ok ? ok({ kind: "Static", value: parsed.value }) : parsed;
4393
+ }
4394
+ return decodeBinding(p, j, parseStaticStringPair, ["", ""]);
4395
+ };
4344
4396
  var parseStaticStringList = (p, v) => {
4345
4397
  if (v.kind === "JNull") return ok([]);
4346
4398
  if (v.kind === "JString" && v.value === OPAQUE2) return ok([OPAQUE2]);
@@ -5188,6 +5240,7 @@ var decodeDisplayKind = (path, j) => {
5188
5240
  );
5189
5241
  }
5190
5242
  };
5243
+ var WRONG_FORM_FIELD_KIND_HINT = schema.FORM_FIELD_KIND_NAMES.join(" | ");
5191
5244
  var decodeFormFieldKind = (autoBind, path, j) => {
5192
5245
  const fo = requireObject(path, j);
5193
5246
  if (!fo.ok) return fo;
@@ -5337,12 +5390,35 @@ var decodeFormFieldKind = (autoBind, path, j) => {
5337
5390
  }
5338
5391
  });
5339
5392
  }
5340
- default:
5341
- return unknownDuCase(
5342
- path,
5343
- d.value,
5344
- "Text | Number | Checkbox | Choice | Range | RangedNumber | SegmentedChoice | TextArea | Date"
5393
+ case "DateRange": {
5394
+ const value = valueOr(
5395
+ decodeBindingStringPair,
5396
+ schema.controlValueDefaults.dateRange,
5397
+ "Binding<[from, to]> ISO-8601 pair value"
5345
5398
  );
5399
+ if (!value.ok) return value;
5400
+ const variant = reqField(path, f, "variant", "DateVariant", decodeDateVariant);
5401
+ if (!variant.ok) return variant;
5402
+ const min = optField(path, f, "min", requireString);
5403
+ if (!min.ok) return min;
5404
+ const max = optField(path, f, "max", requireString);
5405
+ if (!max.ok) return max;
5406
+ const step = optField(path, f, "step", requireFloat);
5407
+ if (!step.ok) return step;
5408
+ return ok({
5409
+ kind: "DateRange",
5410
+ value: value.value,
5411
+ ...onChangeField,
5412
+ variant: variant.value,
5413
+ constraints: {
5414
+ ...min.value !== void 0 ? { min: min.value } : {},
5415
+ ...max.value !== void 0 ? { max: max.value } : {},
5416
+ ...step.value !== void 0 ? { step: step.value } : {}
5417
+ }
5418
+ });
5419
+ }
5420
+ default:
5421
+ return unknownDuCase(path, d.value, WRONG_FORM_FIELD_KIND_HINT);
5346
5422
  }
5347
5423
  };
5348
5424
  var decodeFormField = (path, j) => {
@@ -5828,23 +5904,6 @@ var decodeVisKind = (path, j) => {
5828
5904
  const r = decodeChartSpec(path, j);
5829
5905
  return r.ok ? ok({ kind: "Chart", spec: r.value }) : r;
5830
5906
  }
5831
- // Phase 393 — legacy decode-upgrade: a `Table` tag folds into a read-only `Grid`
5832
- // (the `staticRows` mode). Accepted on read but NEVER re-encodes as `Table` (the
5833
- // encoder has no `Table` arm), riding the Phase 390 retired-kind seam.
5834
- case "Table": {
5835
- const r = decodeStaticRows(path, j);
5836
- return r.ok ? ok({
5837
- kind: "Grid",
5838
- spec: {
5839
- // An opaque Static source re-encodes to {"$type":"Static","value":"<opaque>"} —
5840
- // byte-identical to the F# static grid's `Binding.Static Seq.empty`.
5841
- source: { kind: "Static", value: OPAQUE2 },
5842
- columns: [],
5843
- editable: false,
5844
- staticRows: r.value
5845
- }
5846
- }) : r;
5847
- }
5848
5907
  case "Map": {
5849
5908
  const r = decodeMapSpec(path, j);
5850
5909
  return r.ok ? ok({ kind: "Map", spec: r.value }) : r;
@@ -5936,64 +5995,6 @@ var decodeBox = (path, j) => {
5936
5995
  role: role.value
5937
5996
  });
5938
5997
  };
5939
- var decodeLegacyDashboard = (path, j) => {
5940
- const fo = requireObject(path, j);
5941
- if (!fo.ok) return fo;
5942
- const children = decodeChildren(path, fo.value);
5943
- if (!children.ok) return children;
5944
- return ok({ children: children.value, layout: { kind: "Auto" }, role: "Dashboard" });
5945
- };
5946
- var decodeLegacyStack = (path, j) => {
5947
- const fo = requireObject(path, j);
5948
- if (!fo.ok) return fo;
5949
- const f = fo.value;
5950
- const children = decodeChildren(path, f);
5951
- if (!children.ok) return children;
5952
- const orientation = reqField(path, f, "orientation", "Orientation", decodeOrientation);
5953
- if (!orientation.ok) return orientation;
5954
- const wrap = reqField(path, f, "wrap", "wrap bool", requireBool);
5955
- if (!wrap.ok) return wrap;
5956
- return ok({
5957
- children: children.value,
5958
- layout: { kind: "Flex", direction: orientation.value, wrap: wrap.value },
5959
- role: "Group"
5960
- });
5961
- };
5962
- var decodeLegacyGridLayout = (path, j) => {
5963
- const fo = requireObject(path, j);
5964
- if (!fo.ok) return fo;
5965
- const f = fo.value;
5966
- const children = decodeChildren(path, f);
5967
- if (!children.ok) return children;
5968
- const cols = reqFieldAliased(path, f, "cols", ["columns"], "cols integer", requireInt);
5969
- if (!cols.ok) return cols;
5970
- const templateColumns = optField(path, f, "templateColumns", requireString);
5971
- if (!templateColumns.ok) return templateColumns;
5972
- return ok({
5973
- children: children.value,
5974
- layout: {
5975
- kind: "Grid",
5976
- cols: cols.value,
5977
- ...templateColumns.value !== void 0 ? { templateColumns: templateColumns.value } : {}
5978
- },
5979
- role: "Group"
5980
- });
5981
- };
5982
- var decodeLegacyCard = (path, j) => {
5983
- const fo = requireObject(path, j);
5984
- if (!fo.ok) return fo;
5985
- const f = fo.value;
5986
- const children = decodeChildren(path, f);
5987
- if (!children.ok) return children;
5988
- const heading = optFieldAliased(path, f, "heading", ["title"], decodeTextSource);
5989
- if (!heading.ok) return heading;
5990
- return ok({
5991
- children: children.value,
5992
- ...heading.value !== void 0 ? { heading: heading.value } : {},
5993
- layout: { kind: "Flex", direction: "Vertical", wrap: false },
5994
- role: "Card"
5995
- });
5996
- };
5997
5998
  var decodeSplitPanelSpec = (path, j) => {
5998
5999
  const fo = requireObject(path, j);
5999
6000
  if (!fo.ok) return fo;
@@ -6183,22 +6184,6 @@ var decodeLayoutKind = (path, j) => {
6183
6184
  const r = decodeBox(path, j);
6184
6185
  return r.ok ? ok({ kind: "Box", spec: r.value }) : r;
6185
6186
  }
6186
- case "Dashboard": {
6187
- const r = decodeLegacyDashboard(path, j);
6188
- return r.ok ? ok({ kind: "Box", spec: r.value }) : r;
6189
- }
6190
- case "Stack": {
6191
- const r = decodeLegacyStack(path, j);
6192
- return r.ok ? ok({ kind: "Box", spec: r.value }) : r;
6193
- }
6194
- case "GridLayout": {
6195
- const r = decodeLegacyGridLayout(path, j);
6196
- return r.ok ? ok({ kind: "Box", spec: r.value }) : r;
6197
- }
6198
- case "Card": {
6199
- const r = decodeLegacyCard(path, j);
6200
- return r.ok ? ok({ kind: "Box", spec: r.value }) : r;
6201
- }
6202
6187
  case "SplitPanel": {
6203
6188
  const r = decodeSplitPanelSpec(path, j);
6204
6189
  return r.ok ? ok({ kind: "SplitPanel", spec: r.value }) : r;
@@ -6231,7 +6216,7 @@ var decodeLayoutKind = (path, j) => {
6231
6216
  return unknownDuCase(
6232
6217
  path,
6233
6218
  d.value,
6234
- "Box | Dashboard | Stack | GridLayout | SplitPanel | Tabs | Card | Stepper | SummaryList | Disclosure | Modal | ScrollArea"
6219
+ "Box | SplitPanel | Tabs | Stepper | SummaryList | Disclosure | Modal | ScrollArea"
6235
6220
  );
6236
6221
  }
6237
6222
  };
@@ -6395,6 +6380,13 @@ var decodeEffectClass = (path, j) => {
6395
6380
  );
6396
6381
  return ok({ hostEffect: host.value, determinism: det.value });
6397
6382
  };
6383
+ var WRONG_NODE_KIND_HINT = (() => {
6384
+ const primitives = schema.NODE_KIND_GROUPS.filter((g) => g.label !== "Structural").map(
6385
+ (g) => `${"AEIOU".includes(g.label[0]) ? "an" : "a"} ${g.label} primitive (${g.kinds.join(" | ")})`
6386
+ ).join(", ");
6387
+ const structural = schema.NODE_KIND_GROUPS.find((g) => g.label === "Structural")?.kinds ?? [];
6388
+ return `${primitives}, or ${structural.join(" | ")}`;
6389
+ })();
6398
6390
  var decodeNodeKind = (path, j) => {
6399
6391
  const fo = requireObject(path, j);
6400
6392
  if (!fo.ok) return fo;
@@ -6407,12 +6399,8 @@ var decodeNodeKind = (path, j) => {
6407
6399
  // each primitive to its inner decoder and recover the category here. These
6408
6400
  // name-sets MUST stay in sync with the four inner decoders + the encoder.
6409
6401
  case "Box":
6410
- case "Dashboard":
6411
- case "Stack":
6412
- case "GridLayout":
6413
6402
  case "SplitPanel":
6414
6403
  case "Tabs":
6415
- case "Card":
6416
6404
  case "Stepper":
6417
6405
  case "SummaryList":
6418
6406
  case "Disclosure":
@@ -6451,7 +6439,6 @@ var decodeNodeKind = (path, j) => {
6451
6439
  }
6452
6440
  case "DataGrid":
6453
6441
  case "Chart":
6454
- case "Table":
6455
6442
  case "Map": {
6456
6443
  const k = decodeVisKind(path, j);
6457
6444
  return k.ok ? ok({ kind: "Visualisation", visualisation: k.value }) : k;
@@ -6654,7 +6641,7 @@ var decodeNodeKind = (path, j) => {
6654
6641
  "WRONG_NODE_KIND",
6655
6642
  `${path}.$type`,
6656
6643
  `unknown NodeKind discriminator '${d.value}'`,
6657
- "a Layout primitive (Dashboard | Stack | GridLayout | SplitPanel | Tabs | Card | Stepper | SummaryList | Disclosure | Modal | ScrollArea), a Display primitive (Heading | Markdown | Metric | Badge | Sparkline | Callout | Progress | Skeleton | LabelValueRow | Link | Image | List | Toast), an Input primitive (Form | Filters | Button | FileUpload | Select), a Visualisation primitive (DataGrid | Chart | Table | Map), or Custom | ErrorBoundary | FragmentDecl | FragmentRef | Mount"
6644
+ WRONG_NODE_KIND_HINT
6658
6645
  );
6659
6646
  }
6660
6647
  };
@@ -6825,13 +6812,10 @@ var decodeTreeOpAst = (path, j) => {
6825
6812
  case "InsertChild": {
6826
6813
  const parentJ = reqField(path, f, "parentId", "parent NodeId", requireString);
6827
6814
  if (!parentJ.ok) return parentJ;
6828
- const position = reqField(path, f, "position", "position integer", requireInt);
6829
- if (!position.ok) return position;
6830
6815
  const child = reqField(path, f, "child", "child Node object", decodeNodeAst);
6831
6816
  return child.ok ? ok({
6832
6817
  kind: "InsertChild",
6833
6818
  parentId: parentJ.value,
6834
- position: position.value,
6835
6819
  child: child.value
6836
6820
  }) : child;
6837
6821
  }
@@ -6843,14 +6827,11 @@ var decodeTreeOpAst = (path, j) => {
6843
6827
  const t = target();
6844
6828
  if (!t.ok) return t;
6845
6829
  const newParent = reqField(path, f, "newParentId", "new parent NodeId", requireString);
6846
- if (!newParent.ok) return newParent;
6847
- const newPosition = reqField(path, f, "newPosition", "new position integer", requireInt);
6848
- return newPosition.ok ? ok({
6830
+ return newParent.ok ? ok({
6849
6831
  kind: "MoveNode",
6850
6832
  target: t.value,
6851
- newParentId: newParent.value,
6852
- newPosition: newPosition.value
6853
- }) : newPosition;
6833
+ newParentId: newParent.value
6834
+ }) : newParent;
6854
6835
  }
6855
6836
  case "ReorderChildren": {
6856
6837
  const parentJ = reqField(path, f, "parentId", "parent NodeId", requireString);
@@ -7858,11 +7839,6 @@ var applyOne = (op, root, telem) => {
7858
7839
  "ChildlessKind",
7859
7840
  `Node '${op.parentId}' (kind=${parent.kind.kind}) has no children field \u2014 only Layout kinds accept structural child ops.`
7860
7841
  );
7861
- if (op.position < 0 || op.position > children.length)
7862
- return fail2(
7863
- "PositionOutOfRange",
7864
- `Position ${op.position} is out of range for parent '${op.parentId}' (valid: 0..${children.length}).`
7865
- );
7866
7842
  const existing = new Set(allNodeIds(root));
7867
7843
  const duplicate = allNodeIds(op.child).find((id) => existing.has(id));
7868
7844
  if (duplicate !== void 0)
@@ -7870,11 +7846,7 @@ var applyOne = (op, root, telem) => {
7870
7846
  "DuplicateNodeId",
7871
7847
  `NodeId '${duplicate}' is already present in the tree; ids must be unique.`
7872
7848
  );
7873
- const newChildren = [
7874
- ...children.slice(0, op.position),
7875
- op.child,
7876
- ...children.slice(op.position)
7877
- ];
7849
+ const newChildren = [...children, op.child];
7878
7850
  const newTree = mapNode(op.parentId, (n) => withLayoutChildren(n, newChildren), root);
7879
7851
  if (newTree === void 0)
7880
7852
  return fail2("ParentNotFound", `Parent node '${op.parentId}' not found in tree.`);
@@ -7922,7 +7894,7 @@ var applyOne = (op, root, telem) => {
7922
7894
  const afterRemove = applyOne({ kind: "RemoveNode", target: op.target }, root, []);
7923
7895
  if (!afterRemove.ok) return afterRemove;
7924
7896
  const inserted = applyOne(
7925
- { kind: "InsertChild", parentId: op.newParentId, position: op.newPosition, child: moving },
7897
+ { kind: "InsertChild", parentId: op.newParentId, child: moving },
7926
7898
  afterRemove.value,
7927
7899
  []
7928
7900
  );